Skip to content
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

Draft
wants to merge 27 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,13 @@ jobs:
- name: Checkout awm-relayer repository
uses: actions/checkout@v4

- name: Run E2E Tests
run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego DATA_DIR=/tmp/e2e-test/data ./scripts/e2e_test.sh
- name: Install Forge and Run E2E Tests
# Forge installs to BASE_DIR, but updates the PATH definition in $HOME/.bashrc
run: |
BASE_DIR=${XDG_CONFIG_HOME:-$HOME}
curl -L https://foundry.paradigm.xyz | bash
source $HOME/.bashrc
$BASE_DIR/.foundry/bin/foundryup
export PATH="$PATH:$BASE_DIR/.foundry/bin"
export PATH="$PATH:$GOPATH/bin"
AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego DATA_DIR=/tmp/e2e-test/data ./scripts/e2e_test.sh
14 changes: 13 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import (
"github.com/spf13/viper"
)

// global config singleton
var globalConfig Config

const (
relayerPrivateKeyBytes = 32
accountPrivateKeyEnvVarName = "ACCOUNT_PRIVATE_KEY"
Expand Down Expand Up @@ -77,7 +80,7 @@ func SetDefaultConfigValues(v *viper.Viper) {
v.SetDefault(StorageLocationKey, "./.awm-relayer-storage")
}

// BuildConfig constructs the relayer config using Viper.
// BuildConfig constructs the relayer config using Viper. Also sets the global Config singleton
// The following precedence order is used. Each item takes precedence over the item below it:
// 1. Flags
// 2. Environment variables
Expand Down Expand Up @@ -152,6 +155,8 @@ func BuildConfig(v *viper.Viper) (Config, bool, error) {
}
cfg.PChainAPIURL = pChainapiUrl

globalConfig = cfg

return cfg, optionOverwritten, nil
}

Expand Down Expand Up @@ -232,6 +237,8 @@ func (s *SourceSubnet) Validate() error {
return fmt.Errorf("invalid message contract address in EVM source subnet: %s", messageContractAddress)
}
}
case EVM_BLOCKHASH:
Copy link
Contributor

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?

// No additional validation required
default:
return fmt.Errorf("unsupported VM type for source subnet: %v", s.VM)
}
Expand Down Expand Up @@ -373,3 +380,8 @@ func (s *DestinationSubnet) GetRelayerAccountInfo() (*ecdsa.PrivateKey, common.A
pkBytes = append(pkBytes, pk.PublicKey.Y.Bytes()...)
return pk, common.BytesToAddress(crypto.Keccak256(pkBytes)), nil
}

// Global Config singleton getters
func GetNetworkID() uint32 {
return globalConfig.NetworkID
}
10 changes: 10 additions & 0 deletions config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ type VM int
const (
UNKNOWN_VM VM = iota
EVM
EVM_BLOCKHASH
Copy link
Contributor

Choose a reason for hiding this comment

The 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 EVM_BLOCKHASH subscriber perform a superset of the actions performed byan EVM subscriber?

)

func (vm VM) String() string {
switch vm {
case EVM:
return "evm"
case EVM_BLOCKHASH:
return "evm_blockhash"
default:
return "unknown"
}
Expand All @@ -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
}
Expand All @@ -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"
}
Expand All @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/ava-labs/avalanche-network-runner v1.7.2
github.com/ava-labs/avalanchego v1.10.10
github.com/ava-labs/subnet-evm v0.5.6
github.com/ava-labs/teleporter v0.0.0-20231004221622-b655bfe85981
github.com/ava-labs/teleporter v0.0.0-20231019213726-050fbd0d6992
github.com/ethereum/go-ethereum v1.12.0
github.com/onsi/ginkgo/v2 v2.12.0
github.com/onsi/gomega v1.27.10
Expand Down
18 changes: 12 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,18 @@ github.com/ava-labs/coreth v0.12.5-rc.6 h1:OajGUyKkO5Q82XSuMa8T5UD6QywtCHUiZ4Tv3
github.com/ava-labs/coreth v0.12.5-rc.6/go.mod h1:s5wVyy+5UCCk2m0Tq3jVmy0UqOpKBDYqRE13gInCJVs=
github.com/ava-labs/subnet-evm v0.5.6 h1:u+xBvEExOa362Up02hgSgeF+aqDona57whhRIeEIim8=
github.com/ava-labs/subnet-evm v0.5.6/go.mod h1:desGY3ghT+Ner+oqxeovwF37eM4dmMQbYZECONPQU9w=
github.com/ava-labs/teleporter v0.0.0-20231002210825-d5f6d7f8583a h1:KOj9SYdVCK736YyklPqBgOrlMP7JfN5L88mOl2dPj88=
github.com/ava-labs/teleporter v0.0.0-20231002210825-d5f6d7f8583a/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231004214459-caa08b68d3b4 h1:1gYjG8pi1EnHBZbuXPZKwlzZ6UztlCcaif9o9+CG6K0=
github.com/ava-labs/teleporter v0.0.0-20231004214459-caa08b68d3b4/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231004221622-b655bfe85981 h1:beCzXayPAc9obFYmPyF7WOfyPCIYWThUIB9Jcxbtgq0=
github.com/ava-labs/teleporter v0.0.0-20231004221622-b655bfe85981/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231005141349-feb8fe1523b6 h1:0QOZ/xhAxbP/tTrsTTnufJgC/ZKZPGGzY0SrziM9txQ=
github.com/ava-labs/teleporter v0.0.0-20231005141349-feb8fe1523b6/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231005194159-a8e949bd37c9 h1:Rwrhf+GXl9MX6VX1WQXVGl6JQ6OD4xOI7RMdAQSlzGc=
github.com/ava-labs/teleporter v0.0.0-20231005194159-a8e949bd37c9/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231005202233-cab06cef8e05 h1:qqTnNm4RHxHiErd1LGfJO7lcTgDJYDdnQOofJMEWX/E=
github.com/ava-labs/teleporter v0.0.0-20231005202233-cab06cef8e05/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231013203557-0810228e758d h1:BoxSc7CGlddZuX/DrE6aB3RjaqtP0baRAk0noI3HGbA=
github.com/ava-labs/teleporter v0.0.0-20231013203557-0810228e758d/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231019213504-d5b8203b4952 h1:ovJAh7Mzs/VGKSkzOFeb+lVF7sp5QmEty44HiWUcDv0=
github.com/ava-labs/teleporter v0.0.0-20231019213504-d5b8203b4952/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/ava-labs/teleporter v0.0.0-20231019213726-050fbd0d6992 h1:T5DdAjXJ5tubynurSkjLVasdFCfSbGUishqhtkK9Xsg=
github.com/ava-labs/teleporter v0.0.0-20231019213726-050fbd0d6992/go.mod h1:nyjuYCBefAGCkKDhmJIxh8iTAczapQxwxj/7FC4g9sU=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
Expand Down
2 changes: 1 addition & 1 deletion main/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ func runRelayer(logger logging.Logger,
select {
case txLog := <-subscriber.Logs():
logger.Info(
"Handling Teleporter submit message log.",
"Handling message log.",
zap.String("txId", hex.EncodeToString(txLog.SourceTxID)),
zap.String("originChainId", sourceSubnetInfo.ChainID),
zap.String("destinationChainId", txLog.DestinationChainID.String()),
Expand Down
83 changes: 83 additions & 0 deletions messages/block_hash_publisher/config.go
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably separate these cases. Right now if intValue < 0 we return a nil error.


return uint64(intValue), hasSuffix, nil
}
128 changes: 128 additions & 0 deletions messages/block_hash_publisher/config_test.go
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)
}
}
}
})
}
}
Loading
Loading