-
Notifications
You must be signed in to change notification settings - Fork 19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Block hash publisher #54
base: main
Are you sure you want to change the base?
Changes from all commits
77cfe01
a58c0b3
86c746e
4dc075a
9247214
4e602dc
02ee06b
40c568a
2f54152
76478cc
f95948f
e12913c
ee1d540
d74509a
10ff86d
a3365cb
a70c6ed
ee17f91
1e40f98
191fade
abb2e00
4983965
36c76a3
8bdddf0
8d5cb1c
c3d1639
e209501
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,12 +9,15 @@ type VM int | |
const ( | ||
UNKNOWN_VM VM = iota | ||
EVM | ||
EVM_BLOCKHASH | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be misunderstanding something, but this doesn't feel like it should be a distinct VM, I think it would make more sense as a flag? I also don't really understand how we're using it right now - Does an |
||
) | ||
|
||
func (vm VM) String() string { | ||
switch vm { | ||
case EVM: | ||
return "evm" | ||
case EVM_BLOCKHASH: | ||
return "evm_blockhash" | ||
default: | ||
return "unknown" | ||
} | ||
|
@@ -25,6 +28,8 @@ func ParseVM(vm string) VM { | |
switch vm { | ||
case "evm": | ||
return EVM | ||
case "evm_blockhash": | ||
return EVM_BLOCKHASH | ||
default: | ||
return UNKNOWN_VM | ||
} | ||
|
@@ -36,12 +41,15 @@ type MessageProtocol int | |
const ( | ||
UNKNOWN_MESSAGE_PROTOCOL MessageProtocol = iota | ||
TELEPORTER | ||
BLOCK_HASH_PUBLISHER | ||
) | ||
|
||
func (msg MessageProtocol) String() string { | ||
switch msg { | ||
case TELEPORTER: | ||
return "teleporter" | ||
case BLOCK_HASH_PUBLISHER: | ||
return "block_hash_publisher" | ||
default: | ||
return "unknown" | ||
} | ||
|
@@ -52,6 +60,8 @@ func ParseMessageProtocol(msg string) MessageProtocol { | |
switch msg { | ||
case "teleporter": | ||
return TELEPORTER | ||
case "block_hash_publisher": | ||
return BLOCK_HASH_PUBLISHER | ||
default: | ||
return UNKNOWN_MESSAGE_PROTOCOL | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package block_hash_publisher | ||
|
||
import ( | ||
"encoding/hex" | ||
"fmt" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/ava-labs/avalanchego/ids" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
type destinationInfo struct { | ||
ChainID string `json:"chain-id"` | ||
Address string `json:"address"` | ||
Interval string `json:"interval"` | ||
|
||
useTimeInterval bool | ||
blockInterval uint64 | ||
timeIntervalSeconds uint64 | ||
} | ||
|
||
type Config struct { | ||
DestinationChains []destinationInfo `json:"destination-chains"` | ||
} | ||
|
||
func (c *Config) Validate() error { | ||
for i, destinationInfo := range c.DestinationChains { | ||
// Check if the chainID is valid | ||
if _, err := ids.FromString(destinationInfo.ChainID); err != nil { | ||
return errors.Wrap(err, fmt.Sprintf("invalid chainID in block hash publisher configuration. Provided ID: %s", destinationInfo.ChainID)) | ||
} | ||
|
||
// Check if the address is valid | ||
addr := destinationInfo.Address | ||
if addr == "" { | ||
return errors.New("empty address in block hash publisher configuration") | ||
} | ||
addr = strings.TrimPrefix(addr, "0x") | ||
_, err := hex.DecodeString(addr) | ||
if err != nil { | ||
return errors.Wrap(err, fmt.Sprintf("invalid address in block hash publisher configuration. Provided address: %s", destinationInfo.Address)) | ||
} | ||
Comment on lines
+39
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we able to check that this is a valid address, and not just a valid hex string? |
||
|
||
// Intervals must be either a positive integer, or a positive integer followed by "s" | ||
interval, isSeconds, err := parseIntervalWithSuffix(destinationInfo.Interval) | ||
if err != nil { | ||
return errors.Wrap(err, fmt.Sprintf("invalid interval in block hash publisher configuration. Provided interval: %s", destinationInfo.Interval)) | ||
} | ||
if isSeconds { | ||
c.DestinationChains[i].timeIntervalSeconds = interval | ||
} else { | ||
c.DestinationChains[i].blockInterval = interval | ||
} | ||
c.DestinationChains[i].useTimeInterval = isSeconds | ||
} | ||
return nil | ||
} | ||
|
||
func parseIntervalWithSuffix(input string) (uint64, bool, error) { | ||
// Check if the input string is empty | ||
if input == "" { | ||
return 0, false, fmt.Errorf("empty string") | ||
} | ||
|
||
// Check if the string ends with "s" | ||
hasSuffix := strings.HasSuffix(input, "s") | ||
|
||
// If it has the "s" suffix, remove it | ||
if hasSuffix { | ||
input = input[:len(input)-1] | ||
} | ||
|
||
// Parse the string as an integer | ||
intValue, err := strconv.Atoi(input) | ||
|
||
// Check if the parsed value is a positive integer | ||
if err != nil || intValue < 0 { | ||
return 0, false, err | ||
} | ||
Comment on lines
+78
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably separate these cases. Right now if |
||
|
||
return uint64(intValue), hasSuffix, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package block_hash_publisher | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
type testResult struct { | ||
isTimeInterval bool | ||
blockInterval uint64 | ||
timeIntervalSeconds uint64 | ||
} | ||
|
||
func TestConfigValidate(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
destinationChains []destinationInfo | ||
isError bool | ||
testResults []testResult // indexes correspond to destinationChains | ||
}{ | ||
{ | ||
name: "valid", | ||
destinationChains: []destinationInfo{ | ||
{ | ||
ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", | ||
Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", | ||
Interval: "10", | ||
}, | ||
{ | ||
ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", | ||
Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", | ||
Interval: "10s", | ||
}, | ||
}, | ||
isError: false, | ||
testResults: []testResult{ | ||
{ | ||
isTimeInterval: false, | ||
blockInterval: 10, | ||
}, | ||
{ | ||
isTimeInterval: true, | ||
timeIntervalSeconds: 10, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "invalid chainID", | ||
destinationChains: []destinationInfo{ | ||
{ | ||
ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW7", | ||
Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", | ||
Interval: "10", | ||
}, | ||
}, | ||
isError: true, | ||
}, | ||
{ | ||
name: "invalid interval 1", | ||
destinationChains: []destinationInfo{ | ||
{ | ||
ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", | ||
Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", | ||
Interval: "4r", | ||
}, | ||
}, | ||
isError: true, | ||
}, | ||
{ | ||
name: "invalid interval 2", | ||
destinationChains: []destinationInfo{ | ||
{ | ||
ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", | ||
Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", | ||
Interval: "l", | ||
}, | ||
}, | ||
isError: true, | ||
}, | ||
{ | ||
name: "invalid interval 3", | ||
destinationChains: []destinationInfo{ | ||
{ | ||
ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", | ||
Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", | ||
Interval: "", | ||
}, | ||
}, | ||
isError: true, | ||
}, | ||
{ | ||
name: "invalid address", | ||
destinationChains: []destinationInfo{ | ||
{ | ||
ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", | ||
Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed863", | ||
Interval: "10", | ||
}, | ||
}, | ||
isError: true, | ||
}, | ||
} | ||
|
||
for _, test := range testCases { | ||
t.Run(test.name, func(t *testing.T) { | ||
c := &Config{ | ||
DestinationChains: test.destinationChains, | ||
} | ||
err := c.Validate() | ||
fmt.Println(c) | ||
if test.isError { | ||
require.Error(t, err) | ||
} else { | ||
require.NoError(t, err) | ||
for i, result := range test.testResults { | ||
require.Equal(t, result.isTimeInterval, c.DestinationChains[i].useTimeInterval) | ||
if result.isTimeInterval { | ||
require.Equal(t, result.timeIntervalSeconds, c.DestinationChains[i].timeIntervalSeconds) | ||
} else { | ||
require.Equal(t, result.blockInterval, c.DestinationChains[i].blockInterval) | ||
} | ||
} | ||
} | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems odd that Blockhash is considered its own VM type. Are we overloading the term "VM" here?