diff --git a/examples/interchain.go b/examples/interchain.go new file mode 100644 index 0000000..980ab4b --- /dev/null +++ b/examples/interchain.go @@ -0,0 +1,395 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package examples + +import ( + "fmt" + "math/big" + "os" + "path/filepath" + + "github.com/ava-labs/avalanche-tooling-sdk-go/utils" + + "github.com/ava-labs/avalanche-tooling-sdk-go/avalanche" + "github.com/ava-labs/avalanche-tooling-sdk-go/constants" + "github.com/ava-labs/avalanche-tooling-sdk-go/evm" + "github.com/ava-labs/avalanche-tooling-sdk-go/interchain/interchainmessenger" + "github.com/ava-labs/avalanche-tooling-sdk-go/interchain/relayer" + "github.com/ava-labs/avalanche-tooling-sdk-go/interchain/relayer/localrelayer" + "github.com/ava-labs/avalanche-tooling-sdk-go/key" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/awm-relayer/config" + "github.com/ethereum/go-ethereum/common" +) + +// Deploys ICM in two chains +// Deploys a relayes to interconnect them +// Send an example msg +func InterchainExample( + network avalanche.Network, + chain1RPC string, + chain1PK string, + chain1SubnetID ids.ID, + chain1BlockchainID ids.ID, + chain2RPC string, + chain2PK string, + chain2SubnetID ids.ID, + chain2BlockchainID ids.ID, + relayerDir string, +) error { + // Deploy Interchain Messenger (ICM) + // More information: https://github.com/ava-labs/teleporter + fmt.Println("Deploying Interchain Messenger") + chain1RegistryAddress, chain1MessengerAddress, chain2RegistryAddress, chain2MessengerAddress, err := SetupICM( + chain1RPC, + chain1PK, + chain2RPC, + chain2PK, + ) + if err != nil { + return err + } + + // Create keys for relayer operations: pay fees and receive rewards + // At destination blockchain, the relayer needs to pay fees for smart contract + // calls used to deliver messages. For that, it needs information on properly + // funded private keys at destination. + // Besides this, some source blockchain may provide incentives to relayers + // that send their messages. For that, the relayer needs to be configured with + // the address that will receive such payments at source. + // More information: https: //github.com/ava-labs/awm-relayer/blob/main/relayer/README.md + chain1RelayerKey, err := key.NewSoft() + if err != nil { + return err + } + chain2RelayerKey, err := key.NewSoft() + if err != nil { + return err + } + + // Creates relayer config for the two chains + // The relayer can be configured to listed for new ICM messages + // from a set of source blockchains, and then deliver those + // to a set of destination blockchains. + // Here we are configuring chain1 and chain2 both as source + // and as destination, so we can send messages in any direction. + relayerConfigPath := filepath.Join(relayerDir, "config.json") + relayerStorageDir := filepath.Join(relayerDir, "storage") + relayerConfig, relayerBytes, err := SetupRelayerConf( + relayerStorageDir, + network, + chain1RPC, + chain1SubnetID, + chain1BlockchainID, + chain1RegistryAddress, + chain1MessengerAddress, + chain1RelayerKey, + chain2RPC, + chain2SubnetID, + chain2BlockchainID, + chain2RegistryAddress, + chain2MessengerAddress, + chain2RelayerKey, + ) + if err != nil { + return err + } + if err := os.WriteFile(relayerConfigPath, relayerBytes, constants.WriteReadReadPerms); err != nil { + return err + } + fmt.Printf("Generated relayer conf on %s\n", relayerConfigPath) + + // Fund each relayer key with 10 TOKENs + // Where TOKEN is the native gas token of each blockchain + // Assumes that the TOKEN decimals are 18, so, this equals + // to 1e18 of the smallest gas amount in each chain + fmt.Printf("Funding relayer keys %s, %s\n", chain1RelayerKey.C(), chain2RelayerKey.C()) + desiredRelayerBalance := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) + // chain1PK will have a balance 10 native gas tokens on chain. + if err := relayer.FundRelayer( + relayerConfig, + chain1BlockchainID, + chain1PK, + nil, + desiredRelayerBalance, + ); err != nil { + return err + } + // chain2PK will have a balance 10 native gas tokens on chain2 + if err := relayer.FundRelayer( + relayerConfig, + chain2BlockchainID, + chain2PK, + nil, + desiredRelayerBalance, + ); err != nil { + return err + } + + // install and execute a relayer on localhost + // also wait for proper initialization + relayerLogPath := filepath.Join(relayerDir, "logs.txt") + fmt.Printf("Executing local relayer with logs %s\n", relayerLogPath) + pid, _, err := StartLocalRelayer( + relayerConfigPath, + relayerLogPath, + relayerDir, + ) + if err != nil { + return err + } + + // defer stopping relayer and cleaning up + defer func() { _ = localrelayer.Cleanup(pid, "", relayerStorageDir) }() + + // send a message from chain1 to chain2 + fmt.Println("Verifying message delivery") + if err := TestMessageDelivery( + chain1RPC, + chain1PK, + chain1MessengerAddress, + chain2BlockchainID, + chain2RPC, + chain2MessengerAddress, + []byte("hello world"), + ); err != nil { + return err + } + + fmt.Println("Message successfully delivered") + + return nil +} + +func SetupICM( + chain1RPC string, + chain1PK string, + chain2RPC string, + chain2PK string, +) (string, string, string, string, error) { + // Get latest version of ICM + icmVersion, err := interchainmessenger.GetLatestVersion() + if err != nil { + return "", "", "", "", err + } + // Deploys ICM Messenger and Registry to Chain1 and Chain2 + td := interchainmessenger.Deployer{} + if err := td.DownloadAssets(icmVersion); err != nil { + return "", "", "", "", err + } + _, chain1RegistryAddress, chain1MessengerAddress, err := td.Deploy( + chain1RPC, + chain1PK, + true, + ) + if err != nil { + return "", "", "", "", err + } + _, chain2RegistryAddress, chain2MessengerAddress, err := td.Deploy( + chain2RPC, + chain2PK, + true, + ) + if err != nil { + return "", "", "", "", err + } + return chain1RegistryAddress, chain1MessengerAddress, chain2RegistryAddress, chain2MessengerAddress, nil +} + +func SetupRelayerConf( + storageDir string, + network avalanche.Network, + chain1RPC string, + chain1SubnetID ids.ID, + chain1BlockchainID ids.ID, + chain1RegistryAddress string, + chain1MessengerAddress string, + chain1RelayerKey *key.SoftKey, + chain2RPC string, + chain2SubnetID ids.ID, + chain2BlockchainID ids.ID, + chain2RegistryAddress string, + chain2MessengerAddress string, + chain2RelayerKey *key.SoftKey, +) (*config.Config, []byte, error) { + // Create a base relayer config + config := relayer.CreateBaseRelayerConfig( + logging.Info.LowerString(), + storageDir, + 0, + network, + ) + // Add blockchain chain1 to the relayer config, + // setting it both as source and as destination. + // So the relayer will both listed for new messages in it, + // and send to it new messages from other blockchains. + relayer.AddBlockchainToRelayerConfig( + config, + chain1RPC, + "", + chain1SubnetID, + chain1BlockchainID, + chain1RegistryAddress, + chain1MessengerAddress, + chain1RelayerKey.C(), + chain1RelayerKey.PrivKeyHex(), + ) + // Add blockchain chain2 to the relayer config, + // setting it both as source and as destination. + // So the relayer will both listed for new messages in it, + // and send to it new messages from other blockchains. + relayer.AddBlockchainToRelayerConfig( + config, + chain2RPC, + "", + chain2SubnetID, + chain2BlockchainID, + chain2RegistryAddress, + chain2MessengerAddress, + chain2RelayerKey.C(), + chain2RelayerKey.PrivKeyHex(), + ) + bs, err := relayer.SerializeRelayerConfig(config) + return config, bs, err +} + +func StartLocalRelayer( + configPath string, + logPath string, + installDir string, +) (int, string, error) { + binPath, err := localrelayer.InstallLatest(installDir, "") + if err != nil { + return 0, "", err + } + pid, err := localrelayer.Execute(binPath, configPath, logPath, "") + if err != nil { + if bs, err := os.ReadFile(logPath); err == nil { + fmt.Println(string(bs)) + } + return pid, binPath, err + } + if err := localrelayer.WaitForInitialization(configPath, logPath, 0, 0); err != nil { + return pid, binPath, err + } + return pid, binPath, nil +} + +func TestMessageDelivery( + chain1RPC string, + chain1PK string, + chain1MessengerAddress string, + chain2BlockchainID ids.ID, + chain2RPC string, + chain2MessengerAddress string, + message []byte, +) error { + // send message request to chain1 + tx, receipt, err := interchainmessenger.SendCrossChainMessage( + chain1RPC, + common.HexToAddress(chain1MessengerAddress), + chain1PK, + chain2BlockchainID, + common.Address{}, + message, + ) + if err == evm.ErrFailedReceiptStatus { + txHash := tx.Hash().String() + trace, err := evm.GetTrace(chain1RPC, txHash) + if err != nil { + fmt.Printf("error obtaining tx trace: %s\n", err) + } else { + fmt.Printf("trace: %#v\n", trace) + } + return fmt.Errorf("source receipt status for tx %s is not ReceiptStatusSuccessful", txHash) + } + if err != nil { + return err + } + + // get from chain1 event logs the message id + event, err := evm.GetEventFromLogs(receipt.Logs, interchainmessenger.ParseSendCrossChainMessage) + if err != nil { + return err + } + messageID := event.MessageID + fmt.Println("Source Event Destination Blockchain ID: ", ids.ID(event.DestinationBlockchainID[:])) + fmt.Println("Source Event Message: ", string(event.Message.Message)) + + // wait for chain2 to receive the message + return interchainmessenger.WaitForMessageReception( + chain2RPC, + chain2MessengerAddress, + messageID, + 0, + 0, + ) +} + +// Fuji ICM Example +// +// Deploys ICM into CHAIN1_RPC and CHAIN2_RPC, +// paying deploy fees with CHAIN1_PK and CHAIN2_PK +// +// Downloads and executes a relayer in a local process +// and sets it to listen to CHAIN1 and CHAIN2. +// Subnet IDs and Blockchain IDs are provided to fulfill +// relayer conf +// +// All relayer data is saved into an existing RELAYER_DIR +// +// Example environment setup values: +// export CHAIN1_SUBNET_ID=2AgDoogVySMLkkqfMgzxWSKZWVVmXvNRVixQ5WL7fSUC7sRhTH +// export CHAIN1_BLOCKCHAIN_ID=2Z28jccJqQGCPdF5ee8P3aJq2fsrvyy6F4nhQphGJstTjk9ZsR +// export CHAIN1_RPC=http://36.172.121.333:9650/ext/bc/${CHAIN1_BLOCKCHAIN_ID}/rpc +// export CHAIN1_PK=(64 digit hexadecimal) +// export CHAIN2_SUBNET_ID=2YRENAJfNtBPYB6D4xC1pPV8tn2srdytrsJa6JC3neF2Au5FBc +// export CHAIN2_BLOCKCHAIN_ID=NJT2wqkbNNx9T2cAFsqLNfVVZDamyJyrbRS5fxtevBuMLHwJ8 +// export CHAIN2_RPC=http://36.172.121.123:9650/ext/bc/${CHAIN2_BLOCKCHAIN_ID}/rpc +// export CHAIN2_PK=3c82b8787e887a8798f922d95a948bcffa8d1989898a9898ffffee1000ed7c21 +// export CHAIN2_PK=(64 digit hexadecimal) +// export RELAYER_DIR=~/relayer_rundir/ +func Interchain() error { + chain1RPC := os.Getenv("CHAIN1_RPC") + chain1PK := os.Getenv("CHAIN1_PK") + chain1SubnetID, err := ids.FromString(os.Getenv("CHAIN1_SUBNET_ID")) + if err != nil { + return err + } + chain1BlockchainID, err := ids.FromString(os.Getenv("CHAIN1_BLOCKCHAIN_ID")) + if err != nil { + return err + } + chain2RPC := os.Getenv("CHAIN2_RPC") + chain2PK := os.Getenv("CHAIN2_PK") + chain2SubnetID, err := ids.FromString(os.Getenv("CHAIN2_SUBNET_ID")) + if err != nil { + return err + } + chain2BlockchainID, err := ids.FromString(os.Getenv("CHAIN2_BLOCKCHAIN_ID")) + if err != nil { + return err + } + relayerDir := os.Getenv("RELAYER_DIR") + if relayerDir == "" { + return fmt.Errorf("must define RELAYER_DIR env var") + } + relayerDir = utils.ExpandHome(relayerDir) + if !utils.DirectoryExists(relayerDir) { + return fmt.Errorf("relayer directory %q must exist", relayerDir) + } + return InterchainExample( + avalanche.FujiNetwork(), + chain1RPC, + chain1PK, + chain1SubnetID, + chain1BlockchainID, + chain2RPC, + chain2PK, + chain2SubnetID, + chain2BlockchainID, + relayerDir, + ) +} diff --git a/examples/subnet_ add_validator.go b/examples/subnet_add_validator.go similarity index 100% rename from examples/subnet_ add_validator.go rename to examples/subnet_add_validator.go diff --git a/examples/subnet_ multisig.go b/examples/subnet_multisig.go similarity index 100% rename from examples/subnet_ multisig.go rename to examples/subnet_multisig.go diff --git a/interchain/icm/icm.go b/interchain/interchainmessenger/interchainmessenger.go similarity index 91% rename from interchain/icm/icm.go rename to interchain/interchainmessenger/interchainmessenger.go index c321894..a2a048d 100644 --- a/interchain/icm/icm.go +++ b/interchain/interchainmessenger/interchainmessenger.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package icm +package interchainmessenger import ( "fmt" @@ -138,32 +138,22 @@ func (t *Deployer) DownloadAssets(version string) error { } // Deploys messenger and registry -// If messenger is already deployed, will avoid deploying registry -// (to force, set deployMessenger to false) +// If messenger is already deployed, will avoid deploying registry, +// unless [forceRegistryDeploy] is set func (t *Deployer) Deploy( rpcURL string, privateKey string, - deployMessenger bool, - deployRegistry bool, + forceRegistryDeploy bool, ) (bool, string, string, error) { - var ( - messengerAddress string - registryAddress string - messengerAlreadyDeployed bool - err error + var registryAddress string + messengerAlreadyDeployed, messengerAddress, err := t.DeployMessenger( + rpcURL, + privateKey, ) - if deployMessenger { - messengerAlreadyDeployed, messengerAddress, err = t.DeployMessenger( - rpcURL, - privateKey, - ) - } - if err == nil && deployRegistry { - if !deployMessenger || !messengerAlreadyDeployed { - registryAddress, err = t.DeployRegistry(rpcURL, privateKey) - } + if !messengerAlreadyDeployed || forceRegistryDeploy { + registryAddress, err = t.DeployRegistry(rpcURL, privateKey) } - return messengerAlreadyDeployed, messengerAddress, registryAddress, err + return messengerAlreadyDeployed, registryAddress, messengerAddress, err } func (t *Deployer) DeployMessenger( diff --git a/interchain/interchainmessenger/msg.go b/interchain/interchainmessenger/msg.go new file mode 100644 index 0000000..6368260 --- /dev/null +++ b/interchain/interchainmessenger/msg.go @@ -0,0 +1,170 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package interchainmessenger + +import ( + "fmt" + "math/big" + "time" + + "github.com/ava-labs/avalanche-tooling-sdk-go/evm" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ethereum/go-ethereum/common" +) + +func GetNextMessageID( + rpcURL string, + messengerAddress common.Address, + destinationBlockchainID ids.ID, +) (ids.ID, error) { + out, err := evm.CallToMethod( + rpcURL, + messengerAddress, + "getNextMessageID(bytes32)->(bytes32)", + destinationBlockchainID, + ) + if err != nil { + return ids.Empty, err + } + received, b := out[0].([32]byte) + if !b { + return ids.Empty, fmt.Errorf("error at getNextMessageID call, expected ids.ID, got %T", out[0]) + } + return received, nil +} + +func MessageReceived( + rpcURL string, + messengerAddress common.Address, + messageID ids.ID, +) (bool, error) { + out, err := evm.CallToMethod( + rpcURL, + messengerAddress, + "messageReceived(bytes32)->(bool)", + messageID, + ) + if err != nil { + return false, err + } + received, b := out[0].(bool) + if !b { + return false, fmt.Errorf("error at messageReceived call, expected bool, got %T", out[0]) + } + return received, nil +} + +func WaitForMessageReception( + rpcEndpoint string, + messengerAddress string, + messageID ids.ID, + checkInterval time.Duration, + checkTimeout time.Duration, +) error { + if checkInterval == 0 { + checkInterval = 100 * time.Millisecond + } + if checkTimeout == 0 { + checkTimeout = 10 * time.Second + } + t0 := time.Now() + for { + if b, err := MessageReceived( + rpcEndpoint, + common.HexToAddress(messengerAddress), + messageID, + ); err != nil { + return err + } else if b { + break + } + elapsed := time.Since(t0) + if elapsed > checkTimeout { + return fmt.Errorf("timeout waiting for icm message id %s to be received", messageID.String()) + } + time.Sleep(checkInterval) + } + return nil +} + +func SendCrossChainMessage( + rpcURL string, + messengerAddress common.Address, + privateKey string, + destinationBlockchainID ids.ID, + destinationAddress common.Address, + message []byte, +) (*types.Transaction, *types.Receipt, error) { + type FeeInfo struct { + FeeTokenAddress common.Address + Amount *big.Int + } + type Params struct { + DestinationBlockchainID [32]byte + DestinationAddress common.Address + FeeInfo FeeInfo + RequiredGasLimit *big.Int + AllowedRelayerAddresses []common.Address + Message []byte + } + params := Params{ + DestinationBlockchainID: destinationBlockchainID, + DestinationAddress: destinationAddress, + FeeInfo: FeeInfo{ + FeeTokenAddress: common.Address{}, + Amount: big.NewInt(0), + }, + RequiredGasLimit: big.NewInt(1), + AllowedRelayerAddresses: []common.Address{}, + Message: message, + } + return evm.TxToMethod( + rpcURL, + privateKey, + messengerAddress, + nil, + "sendCrossChainMessage((bytes32, address, (address, uint256), uint256, [address], bytes))->(bytes32)", + params, + ) +} + +// events + +type TeleporterMessageReceipt struct { + ReceivedMessageNonce *big.Int + RelayerRewardAddress common.Address +} +type TeleporterFeeInfo struct { + FeeTokenAddress common.Address + Amount *big.Int +} +type TeleporterMessage struct { + MessageNonce *big.Int + OriginSenderAddress common.Address + DestinationBlockchainID [32]byte + DestinationAddress common.Address + RequiredGasLimit *big.Int + AllowedRelayerAddresses []common.Address + Receipts []TeleporterMessageReceipt + Message []byte +} +type TeleporterMessengerSendCrossChainMessage struct { + MessageID [32]byte + DestinationBlockchainID [32]byte + Message TeleporterMessage + FeeInfo TeleporterFeeInfo +} + +func ParseSendCrossChainMessage(log types.Log) (*TeleporterMessengerSendCrossChainMessage, error) { + event := new(TeleporterMessengerSendCrossChainMessage) + if err := evm.UnpackLog( + "SendCrossChainMessage(bytes32,bytes32,(uint256,address,bytes32,address,uint256,[address],[(uint256,address)],bytes),(address,uint256))", + []int{0, 1}, + log, + event, + ); err != nil { + return nil, err + } + return event, nil +} diff --git a/interchain/relayer/conf.go b/interchain/relayer/conf.go index 617a4dd..9c92a17 100644 --- a/interchain/relayer/conf.go +++ b/interchain/relayer/conf.go @@ -6,11 +6,9 @@ import ( "encoding/json" "fmt" "math/big" - "os" "strings" "github.com/ava-labs/avalanche-tooling-sdk-go/avalanche" - "github.com/ava-labs/avalanche-tooling-sdk-go/constants" "github.com/ava-labs/avalanche-tooling-sdk-go/evm" "github.com/ava-labs/avalanche-tooling-sdk-go/utils" "github.com/ava-labs/avalanchego/ids" @@ -19,8 +17,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" ) -var defaultRequiredBalance = big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(500)) // 500 AVAX - func GetSourceConfig( relayerConfig *config.Config, blockchainID ids.ID, @@ -53,10 +49,12 @@ func GetDestinationConfig( return nil } -// fund the relayer private key associated to [blockchainID] -// at [relayerConfig]. if [amount] > 0 transfers it to the account. if, -// afterwards, balance < [requiredMinBalance], transfers remaining amount for that -// if [requiredMinBalance] is nil, uses [defaultRequiredBalance] +// FundRelayer funds the relayer private key associated to [blockchainID] specified in +// [relayerConfig]. Receives one of two amount specs: +// 1) if [amount] > 0, transfers it to the account. +// 2) if [requiredMinBalance] > 0, checks the balance in the account, and +// if balance < [requiredMinBalance], transfers the amount needed so as +// balance == [requiredMinBalance] func FundRelayer( relayerConfig *config.Config, blockchainID ids.ID, @@ -77,7 +75,7 @@ func FundRelayer( ) } -// fund [relayerPrivateKey] at [rpcEndpoint] +// FundRelayerPrivateKey funds [relayerPrivateKey] at [rpcEndpoint] // see FundRelayer for [amount]/[requiredMinBalance] logic func FundRelayerPrivateKey( rpcEndpoint string, @@ -100,7 +98,7 @@ func FundRelayerPrivateKey( ) } -// fund [relayerAddress] at [rpcEndpoint] +// FundRelayerAddress funds [relayerAddress] at [rpcEndpoint] // see FundRelayer for [amount]/[requiredMinBalance] logic func FundRelayerAddress( rpcEndpoint string, @@ -109,8 +107,8 @@ func FundRelayerAddress( amount *big.Int, requiredMinBalance *big.Int, ) error { - if requiredMinBalance == nil { - requiredMinBalance = defaultRequiredBalance + if amount == nil && requiredMinBalance == nil { + return fmt.Errorf("failure funding relayer: you must provide an amount or a required balance") } client, err := evm.GetClient(rpcEndpoint) if err != nil { @@ -134,119 +132,19 @@ func FundRelayerAddress( ) } -func LoadRelayerConfig(relayerConfigPath string) (*config.Config, error) { +func UnserializeRelayerConfig(relayerConfigBytes []byte) (*config.Config, error) { relayerConfig := config.Config{} - bs, err := os.ReadFile(relayerConfigPath) - if err != nil { - return nil, err - } - if err := json.Unmarshal(bs, &relayerConfig); err != nil { + if err := json.Unmarshal(relayerConfigBytes, &relayerConfig); err != nil { return nil, err } return &relayerConfig, nil } -func SaveRelayerConfig(relayerConfig *config.Config, relayerConfigPath string) error { - bs, err := json.MarshalIndent(relayerConfig, "", " ") - if err != nil { - return err - } - return os.WriteFile(relayerConfigPath, bs, constants.WriteReadReadPerms) -} - -func CreateBaseRelayerConfigFile( - relayerConfigPath string, - logLevel string, - storageLocation string, - metricsPort uint16, - network avalanche.Network, -) error { - relayerConfig := CreateBaseRelayerConfig( - logLevel, - storageLocation, - metricsPort, - network, - ) - return SaveRelayerConfig(relayerConfig, relayerConfigPath) -} - -func AddSourceToRelayerConfigFile( - relayerConfigPath string, - rpcEndpoint string, - wsEndpoint string, - subnetID ids.ID, - blockchainID ids.ID, - icmRegistryAddress string, - icmMessengerAddress string, - relayerRewardAddress string, -) error { - relayerConfig, err := LoadRelayerConfig(relayerConfigPath) - if err != nil { - return err - } - AddSourceToRelayerConfig( - relayerConfig, - rpcEndpoint, - wsEndpoint, - subnetID, - blockchainID, - icmRegistryAddress, - icmMessengerAddress, - relayerRewardAddress, - ) - return SaveRelayerConfig(relayerConfig, relayerConfigPath) -} - -func AddDestinationToRelayerConfigFile( - relayerConfigPath string, - rpcEndpoint string, - subnetID ids.ID, - blockchainID ids.ID, - relayerPrivateKey string, -) error { - relayerConfig, err := LoadRelayerConfig(relayerConfigPath) - if err != nil { - return err - } - AddDestinationToRelayerConfig( - relayerConfig, - rpcEndpoint, - subnetID, - blockchainID, - relayerPrivateKey, - ) - return SaveRelayerConfig(relayerConfig, relayerConfigPath) -} - -func AddBlockchainToRelayerConfigFile( - relayerConfigPath string, - rpcEndpoint string, - wsEndpoint string, - subnetID ids.ID, - blockchainID ids.ID, - icmRegistryAddress string, - icmMessengerAddress string, - relayerRewardAddress string, - relayerPrivateKey string, -) error { - relayerConfig, err := LoadRelayerConfig(relayerConfigPath) - if err != nil { - return err - } - AddBlockchainToRelayerConfig( - relayerConfig, - rpcEndpoint, - wsEndpoint, - subnetID, - blockchainID, - icmRegistryAddress, - icmMessengerAddress, - relayerRewardAddress, - relayerPrivateKey, - ) - return SaveRelayerConfig(relayerConfig, relayerConfigPath) +func SerializeRelayerConfig(relayerConfig *config.Config) ([]byte, error) { + return json.MarshalIndent(relayerConfig, "", " ") } +// Creates a base relayer config func CreateBaseRelayerConfig( logLevel string, storageLocation string, @@ -273,6 +171,9 @@ func CreateBaseRelayerConfig( return relayerConfig } +// Adds blockchain to the relayer config, setting it as source. +// So the relayer will listed for new messages in it, +// sending those to other blockchains. func AddSourceToRelayerConfig( relayerConfig *config.Config, rpcEndpoint string, @@ -319,6 +220,9 @@ func AddSourceToRelayerConfig( } } +// Adds a blockchain to the relayer config, +// setting it as destination. +// So the relayer will send to it new messages from other blockchains. func AddDestinationToRelayerConfig( relayerConfig *config.Config, rpcEndpoint string, @@ -340,6 +244,10 @@ func AddDestinationToRelayerConfig( } } +// Adds a blockchain to the relayer config, +// setting it both as source and as destination. +// So the relayer will both listed for new messages in it, +// and send to it new messages from other blockchains. func AddBlockchainToRelayerConfig( relayerConfig *config.Config, rpcEndpoint string, diff --git a/interchain/relayer/local.go b/interchain/relayer/localrelayer/localrelayer.go similarity index 61% rename from interchain/relayer/local.go rename to interchain/relayer/localrelayer/localrelayer.go index 0a47bcc..e49b195 100644 --- a/interchain/relayer/local.go +++ b/interchain/relayer/localrelayer/localrelayer.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved // See the file LICENSE for licensing terms. -package relayer +package localrelayer import ( "fmt" @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanche-tooling-sdk-go/constants" "github.com/ava-labs/avalanche-tooling-sdk-go/install" + "github.com/ava-labs/avalanche-tooling-sdk-go/interchain/relayer" "github.com/ava-labs/avalanche-tooling-sdk-go/process" ) @@ -76,6 +77,56 @@ func Execute( return process.Execute(binPath, args, logWriter, logWriter, runFilePath, localRelayerSetupTime) } +func WaitForInitialization( + configPath string, + logPath string, + checkInterval time.Duration, + checkTimeout time.Duration, +) error { + configBytes, err := os.ReadFile(configPath) + if err != nil { + return err + } + config, err := relayer.UnserializeRelayerConfig(configBytes) + if err != nil { + return err + } + sourceBlockchains := []string{} + for _, source := range config.SourceBlockchains { + sourceBlockchains = append(sourceBlockchains, source.BlockchainID) + } + if checkInterval == 0 { + checkInterval = 100 * time.Millisecond + } + if checkTimeout == 0 { + checkTimeout = 10 * time.Second + } + t0 := time.Now() + for { + bs, err := os.ReadFile(logPath) + if err != nil { + return err + } + sourcesInitialized := 0 + for _, l := range strings.Split(string(bs), "\n") { + for _, sourceBlockchain := range sourceBlockchains { + if strings.Contains(l, "Listener initialized") && strings.Contains(l, sourceBlockchain) { + sourcesInitialized++ + } + } + } + if sourcesInitialized == len(sourceBlockchains) { + break + } + elapsed := time.Since(t0) + if elapsed > checkTimeout { + return fmt.Errorf("timeout waiting for relayer initialization") + } + time.Sleep(checkInterval) + } + return nil +} + func IsRunning(pid int, runFilePath string) (bool, int, *os.Process, error) { return process.IsRunning(pid, runFilePath) } diff --git a/node/dockerCompose.go b/node/docker_compose.go similarity index 100% rename from node/dockerCompose.go rename to node/docker_compose.go diff --git a/node/dockerConfig.go b/node/docker_config.go similarity index 100% rename from node/dockerConfig.go rename to node/docker_config.go diff --git a/node/dockerImage.go b/node/docker_image.go similarity index 100% rename from node/dockerImage.go rename to node/docker_image.go diff --git a/node/dockerSsh.go b/node/docker_ssh.go similarity index 100% rename from node/dockerSsh.go rename to node/docker_ssh.go diff --git a/node/nodeResult.go b/node/node_result.go similarity index 100% rename from node/nodeResult.go rename to node/node_result.go diff --git a/vm/evmSettings.go b/vm/evm_settings.go similarity index 100% rename from vm/evmSettings.go rename to vm/evm_settings.go