From 77cfe01b8fb4a9509919433726af47b685ad1a47 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 27 Sep 2023 15:40:56 +0000 Subject: [PATCH 01/26] block hash config --- config/types.go | 10 ++ messages/block_hash_publisher/config.go | 70 +++++++++++ messages/block_hash_publisher/config_test.go | 112 ++++++++++++++++++ .../block_hash_publisher/message_manager.go | 63 ++++++++++ messages/message_manager.go | 11 +- relayer/message_relayer.go | 5 +- vms/contract_message.go | 1 + vms/destination_client.go | 1 + vms/subscriber.go | 1 + 9 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 messages/block_hash_publisher/config.go create mode 100644 messages/block_hash_publisher/config_test.go create mode 100644 messages/block_hash_publisher/message_manager.go diff --git a/config/types.go b/config/types.go index 1edd98ad..2c32babd 100644 --- a/config/types.go +++ b/config/types.go @@ -9,12 +9,15 @@ type VM int const ( UNKNOWN_VM VM = iota EVM + EVM_BLOCKHASH ) 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 } diff --git a/messages/block_hash_publisher/config.go b/messages/block_hash_publisher/config.go new file mode 100644 index 00000000..33d08e18 --- /dev/null +++ b/messages/block_hash_publisher/config.go @@ -0,0 +1,70 @@ +package block_hash_publisher + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/pkg/errors" +) + +type destinationInfo struct { + ChainID string `json:"chain-id"` + Interval string `json:"interval"` + + useTimeInterval bool + blockInterval int + timeIntervalSeconds time.Duration +} + +type Config struct { + DestinationChains []destinationInfo `json:"destination-chains"` +} + +func (c *Config) Validate() error { + for i, destinationInfo := range c.DestinationChains { + if _, err := ids.FromString(destinationInfo.ChainID); err != nil { + return errors.Wrap(err, fmt.Sprintf("invalid subnetID in block hash publisher configuration. Provided ID: %s", destinationInfo.ChainID)) + } + + // Intervals must be either a positive integer, or a positive integer followed by "s" + interval, isSeconds, err := parsePositiveIntWithSuffix(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 = time.Duration(interval) * time.Second + } else { + c.DestinationChains[i].blockInterval = interval + } + c.DestinationChains[i].useTimeInterval = isSeconds + } + return nil +} + +func parsePositiveIntWithSuffix(input string) (int, 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 + } + + return intValue, hasSuffix, nil +} diff --git a/messages/block_hash_publisher/config_test.go b/messages/block_hash_publisher/config_test.go new file mode 100644 index 00000000..0052fa29 --- /dev/null +++ b/messages/block_hash_publisher/config_test.go @@ -0,0 +1,112 @@ +package block_hash_publisher + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +type testResult struct { + isTimeInterval bool + blockInterval int + timeIntervalSeconds time.Duration +} + +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", + Interval: "10", + }, + { + ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Interval: "10s", + }, + }, + isError: false, + testResults: []testResult{ + { + isTimeInterval: false, + blockInterval: 10, + }, + { + isTimeInterval: true, + timeIntervalSeconds: 10 * time.Second, + }, + }, + }, + { + name: "invalid chainID", + destinationChains: []destinationInfo{ + { + ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW7", + Interval: "10", + }, + }, + isError: true, + }, + { + name: "invalid interval 1", + destinationChains: []destinationInfo{ + { + ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Interval: "4r", + }, + }, + isError: true, + }, + { + name: "invalid interval 2", + destinationChains: []destinationInfo{ + { + ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Interval: "l", + }, + }, + isError: true, + }, + { + name: "invalid interval 3", + destinationChains: []destinationInfo{ + { + ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Interval: "", + }, + }, + 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) + } + } + } + }) + } +} diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go new file mode 100644 index 00000000..ed8f2ec9 --- /dev/null +++ b/messages/block_hash_publisher/message_manager.go @@ -0,0 +1,63 @@ +package block_hash_publisher + +import ( + "encoding/json" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/awm-relayer/config" + "github.com/ava-labs/awm-relayer/vms" + "github.com/ava-labs/awm-relayer/vms/vmtypes" + "github.com/ethereum/go-ethereum/common" + "go.uber.org/zap" +) + +type messageManager struct { + messageConfig Config + destinationClients map[ids.ID]vms.DestinationClient + logger logging.Logger +} + +func NewMessageManager( + logger logging.Logger, + messageProtocolAddress common.Hash, + messageProtocolConfig config.MessageProtocolConfig, + destinationClients map[ids.ID]vms.DestinationClient, +) (*messageManager, error) { + // Marshal the map and unmarshal into the Teleporter config + data, err := json.Marshal(messageProtocolConfig.Settings) + if err != nil { + logger.Error("Failed to marshal Block Hash Publisher config") + return nil, err + } + var messageConfig Config + if err := json.Unmarshal(data, &messageConfig); err != nil { + logger.Error("Failed to unmarshal Block Hash Publisher config") + return nil, err + } + + if err := messageConfig.Validate(); err != nil { + logger.Error( + "Invalid Block Hash Publisher config.", + zap.Error(err), + ) + return nil, err + } + + return &messageManager{ + messageConfig: messageConfig, + destinationClients: destinationClients, + logger: logger, + }, nil +} + +// ShouldSendMessage returns true if the message should be sent to the destination chain +func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) { + // TODONOW: send the message to the destination chain + return true, nil +} + +func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { + return nil +} diff --git a/messages/message_manager.go b/messages/message_manager.go index 00251353..8a649430 100644 --- a/messages/message_manager.go +++ b/messages/message_manager.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/awm-relayer/config" + "github.com/ava-labs/awm-relayer/messages/block_hash_publisher" "github.com/ava-labs/awm-relayer/messages/teleporter" "github.com/ava-labs/awm-relayer/vms" "github.com/ava-labs/awm-relayer/vms/vmtypes" @@ -40,7 +41,15 @@ func NewMessageManager( format := messageProtocolConfig.MessageFormat switch config.ParseMessageProtocol(format) { case config.TELEPORTER: - return teleporter.NewMessageManager(logger, + return teleporter.NewMessageManager( + logger, + messageProtocolAddress, + messageProtocolConfig, + destinationClients, + ) + case config.BLOCK_HASH_PUBLISHER: + return block_hash_publisher.NewMessageManager( + logger, messageProtocolAddress, messageProtocolConfig, destinationClients, diff --git a/relayer/message_relayer.go b/relayer/message_relayer.go index a88ca365..5ddff08f 100644 --- a/relayer/message_relayer.go +++ b/relayer/message_relayer.go @@ -60,7 +60,7 @@ func newMessageRelayer( logger logging.Logger, metrics *MessageRelayerMetrics, relayer *Relayer, - warpMessage *warp.UnsignedMessage, + warpMessage *warp.UnsignedMessage, // TODONOW: store WarpMessageInfo destinationChainID ids.ID, messageResponseChan chan message.InboundMessage, messageCreator message.Creator, @@ -76,7 +76,9 @@ func newMessageRelayer( } } +// TODONOW: remove WarpMessageInfo param func (r *messageRelayer) relayMessage(warpMessageInfo *vmtypes.WarpMessageInfo, requestID uint32, messageManager messages.MessageManager) error { + // TODONOW: blockPublisher messageManager should decide based on configured time/block interval shouldSend, err := messageManager.ShouldSendMessage(warpMessageInfo, r.destinationChainID) if err != nil { r.logger.Error( @@ -108,6 +110,7 @@ func (r *messageRelayer) relayMessage(warpMessageInfo *vmtypes.WarpMessageInfo, // create signed message latency (ms) r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds())) + // TODONOW: blockPublisher messageManager should send message to destination chain err = messageManager.SendMessage(signedMessage, warpMessageInfo.WarpPayload, r.destinationChainID) if err != nil { r.logger.Error( diff --git a/vms/contract_message.go b/vms/contract_message.go index 3cb9fdb7..b07f1e62 100644 --- a/vms/contract_message.go +++ b/vms/contract_message.go @@ -15,6 +15,7 @@ type ContractMessage interface { UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.WarpMessageInfo, error) } +// TODONOW: add evm_blockhash type func NewContractMessage(logger logging.Logger, subnetInfo config.SourceSubnet) ContractMessage { switch config.ParseVM(subnetInfo.VM) { case config.EVM: diff --git a/vms/destination_client.go b/vms/destination_client.go index 1d5f88f8..d6611dc0 100644 --- a/vms/destination_client.go +++ b/vms/destination_client.go @@ -34,6 +34,7 @@ type DestinationClient interface { DestinationChainID() ids.ID } +// TODONOW: evm_blockhash type creates new evm client func NewDestinationClient(logger logging.Logger, subnetInfo config.DestinationSubnet) (DestinationClient, error) { switch config.ParseVM(subnetInfo.VM) { case config.EVM: diff --git a/vms/subscriber.go b/vms/subscriber.go index 82682cca..95a80311 100644 --- a/vms/subscriber.go +++ b/vms/subscriber.go @@ -39,6 +39,7 @@ type Subscriber interface { Cancel() } +// TODONOW: add evm_blockhash type // NewSubscriber returns a concrete Subscriber according to the VM specified by [subnetInfo] func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db database.RelayerDatabase) Subscriber { switch config.ParseVM(subnetInfo.VM) { From a58c0b3d54f6a61dc686de1b45aa3eb218c59909 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 27 Sep 2023 15:45:25 +0000 Subject: [PATCH 02/26] clean up message relayer params --- relayer/message_relayer.go | 27 ++++++++++++++------------- relayer/relayer.go | 4 ++-- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/relayer/message_relayer.go b/relayer/message_relayer.go index 5ddff08f..a3320c68 100644 --- a/relayer/message_relayer.go +++ b/relayer/message_relayer.go @@ -48,7 +48,7 @@ var ( // Each messageRelayer runs in its own goroutine. type messageRelayer struct { relayer *Relayer - warpMessage *warp.UnsignedMessage + warpMessageInfo *vmtypes.WarpMessageInfo destinationChainID ids.ID messageResponseChan chan message.InboundMessage logger logging.Logger @@ -60,14 +60,14 @@ func newMessageRelayer( logger logging.Logger, metrics *MessageRelayerMetrics, relayer *Relayer, - warpMessage *warp.UnsignedMessage, // TODONOW: store WarpMessageInfo + warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID, messageResponseChan chan message.InboundMessage, messageCreator message.Creator, ) *messageRelayer { return &messageRelayer{ relayer: relayer, - warpMessage: warpMessage, + warpMessageInfo: warpMessageInfo, destinationChainID: destinationChainID, messageResponseChan: messageResponseChan, logger: logger, @@ -76,10 +76,9 @@ func newMessageRelayer( } } -// TODONOW: remove WarpMessageInfo param -func (r *messageRelayer) relayMessage(warpMessageInfo *vmtypes.WarpMessageInfo, requestID uint32, messageManager messages.MessageManager) error { +func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages.MessageManager) error { // TODONOW: blockPublisher messageManager should decide based on configured time/block interval - shouldSend, err := messageManager.ShouldSendMessage(warpMessageInfo, r.destinationChainID) + shouldSend, err := messageManager.ShouldSendMessage(r.warpMessageInfo, r.destinationChainID) if err != nil { r.logger.Error( "Failed to check if message should be sent", @@ -111,7 +110,7 @@ func (r *messageRelayer) relayMessage(warpMessageInfo *vmtypes.WarpMessageInfo, r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds())) // TODONOW: blockPublisher messageManager should send message to destination chain - err = messageManager.SendMessage(signedMessage, warpMessageInfo.WarpPayload, r.destinationChainID) + err = messageManager.SendMessage(signedMessage, r.warpMessageInfo.WarpPayload, r.destinationChainID) if err != nil { r.logger.Error( "Failed to send warp message", @@ -135,6 +134,8 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e zap.String("destinationChainID", r.destinationChainID.String()), ) + sourceChainID := r.warpMessageInfo.WarpUnsignedMessage.SourceChainID + // Get the current canonical validator set of the source subnet. validatorSet, totalValidatorWeight, err := r.getCurrentCanonicalValidatorSet() if err != nil { @@ -177,7 +178,7 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e // Construct the request req := msg.SignatureRequest{ - MessageID: r.warpMessage.ID(), + MessageID: r.warpMessageInfo.WarpUnsignedMessage.ID(), } reqBytes, err := msg.RequestToBytes(codec, req) if err != nil { @@ -189,7 +190,7 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e return nil, err } - outMsg, err := r.messageCreator.AppRequest(r.warpMessage.SourceChainID, requestID, peers.DefaultAppRequestTimeout, reqBytes) + outMsg, err := r.messageCreator.AppRequest(sourceChainID, requestID, peers.DefaultAppRequestTimeout, reqBytes) if err != nil { r.logger.Error( "Failed to create app request message", @@ -232,8 +233,8 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e // Register a timeout response for each queried node reqID := ids.RequestID{ NodeID: nodeID, - SourceChainID: r.warpMessage.SourceChainID, - DestinationChainID: r.warpMessage.SourceChainID, + SourceChainID: sourceChainID, + DestinationChainID: sourceChainID, RequestID: requestID, Op: byte(message.AppResponseOp), } @@ -325,7 +326,7 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e return nil, err } - signedMsg, err := warp.NewMessage(r.warpMessage, &warp.BitSetSignature{ + signedMsg, err := warp.NewMessage(r.warpMessageInfo.WarpUnsignedMessage, &warp.BitSetSignature{ Signers: vdrBitSet.Bytes(), Signature: *(*[bls.SignatureLen]byte)(bls.SignatureToBytes(aggSig)), }) @@ -474,7 +475,7 @@ func (r *messageRelayer) isValidSignatureResponse( return blsSignatureBuf{}, false } - if !bls.Verify(pubKey, sig, r.warpMessage.Bytes()) { + if !bls.Verify(pubKey, sig, r.warpMessageInfo.WarpUnsignedMessage.Bytes()) { r.logger.Debug( "Failed verification for signature", zap.String("destinationChainID", r.destinationChainID.String()), diff --git a/relayer/relayer.go b/relayer/relayer.go index 6513f3c9..4fac74c9 100644 --- a/relayer/relayer.go +++ b/relayer/relayer.go @@ -207,7 +207,7 @@ func (r *Relayer) RelayMessage(warpLogInfo *vmtypes.WarpLogInfo, metrics *Messag } // Create and run the message relayer to attempt to deliver the message to the destination chain - messageRelayer := newMessageRelayer(r.logger, metrics, r, warpMessageInfo.WarpUnsignedMessage, warpLogInfo.DestinationChainID, r.responseChan, messageCreator) + messageRelayer := newMessageRelayer(r.logger, metrics, r, warpMessageInfo, warpLogInfo.DestinationChainID, r.responseChan, messageCreator) if err != nil { r.logger.Error( "Failed to create message relayer", @@ -218,7 +218,7 @@ func (r *Relayer) RelayMessage(warpLogInfo *vmtypes.WarpLogInfo, metrics *Messag // Relay the message to the destination. Messages from a given source chain must be processed in serial in order to // guarantee that the previous block (n-1) is fully processed by the relayer when processing a given log from block n. - err = messageRelayer.relayMessage(warpMessageInfo, r.currentRequestID, messageManager) + err = messageRelayer.relayMessage(r.currentRequestID, messageManager) if err != nil { r.logger.Error( "Failed to run message relayer", From 86c746eea4192aca6533c84ee2b3fcbb59bf3d31 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 27 Sep 2023 18:09:39 +0000 Subject: [PATCH 03/26] wip --- config/config.go | 12 +- .../block_hash_publisher/message_manager.go | 3 +- relayer/message_relayer.go | 2 - vms/contract_message.go | 4 +- vms/destination_client.go | 1 - vms/evm_block_hash/contract_message.go | 54 +++++ vms/evm_block_hash/subscriber.go | 187 ++++++++++++++++++ vms/subscriber.go | 4 +- 8 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 vms/evm_block_hash/contract_message.go create mode 100644 vms/evm_block_hash/subscriber.go diff --git a/config/config.go b/config/config.go index 783e11be..5210ab78 100644 --- a/config/config.go +++ b/config/config.go @@ -22,6 +22,9 @@ import ( "github.com/spf13/viper" ) +// global config singleton +var globalConfig Config + const ( relayerPrivateKeyBytes = 32 accountPrivateKeyEnvVarName = "ACCOUNT_PRIVATE_KEY" @@ -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 @@ -152,6 +155,8 @@ func BuildConfig(v *viper.Viper) (Config, bool, error) { } cfg.PChainAPIURL = pChainapiUrl + globalConfig = cfg + return cfg, optionOverwritten, nil } @@ -373,3 +378,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 +} diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index ed8f2ec9..3bf6c09e 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -54,10 +54,11 @@ func NewMessageManager( // ShouldSendMessage returns true if the message should be sent to the destination chain func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) { - // TODONOW: send the message to the destination chain + // TODONOW: check if the message should be sent to the destination chain based on the configured block/time interval return true, nil } func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { + // TODONOW: send the message to the destination chain return nil } diff --git a/relayer/message_relayer.go b/relayer/message_relayer.go index a3320c68..b7bf77a5 100644 --- a/relayer/message_relayer.go +++ b/relayer/message_relayer.go @@ -77,7 +77,6 @@ func newMessageRelayer( } func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages.MessageManager) error { - // TODONOW: blockPublisher messageManager should decide based on configured time/block interval shouldSend, err := messageManager.ShouldSendMessage(r.warpMessageInfo, r.destinationChainID) if err != nil { r.logger.Error( @@ -109,7 +108,6 @@ func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages. // create signed message latency (ms) r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds())) - // TODONOW: blockPublisher messageManager should send message to destination chain err = messageManager.SendMessage(signedMessage, r.warpMessageInfo.WarpPayload, r.destinationChainID) if err != nil { r.logger.Error( diff --git a/vms/contract_message.go b/vms/contract_message.go index b07f1e62..4b032258 100644 --- a/vms/contract_message.go +++ b/vms/contract_message.go @@ -7,6 +7,7 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/vms/evm" + "github.com/ava-labs/awm-relayer/vms/evm_block_hash" "github.com/ava-labs/awm-relayer/vms/vmtypes" ) @@ -15,11 +16,12 @@ type ContractMessage interface { UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.WarpMessageInfo, error) } -// TODONOW: add evm_blockhash type func NewContractMessage(logger logging.Logger, subnetInfo config.SourceSubnet) ContractMessage { switch config.ParseVM(subnetInfo.VM) { case config.EVM: return evm.NewContractMessage(logger, subnetInfo) + case config.EVM_BLOCKHASH: + return evm_block_hash.NewContractMessage(logger, subnetInfo) default: return nil } diff --git a/vms/destination_client.go b/vms/destination_client.go index d6611dc0..1d5f88f8 100644 --- a/vms/destination_client.go +++ b/vms/destination_client.go @@ -34,7 +34,6 @@ type DestinationClient interface { DestinationChainID() ids.ID } -// TODONOW: evm_blockhash type creates new evm client func NewDestinationClient(logger logging.Logger, subnetInfo config.DestinationSubnet) (DestinationClient, error) { switch config.ParseVM(subnetInfo.VM) { case config.EVM: diff --git a/vms/evm_block_hash/contract_message.go b/vms/evm_block_hash/contract_message.go new file mode 100644 index 00000000..d6319808 --- /dev/null +++ b/vms/evm_block_hash/contract_message.go @@ -0,0 +1,54 @@ +package evm_block_hash + +import ( + "github.com/ava-labs/avalanchego/utils/logging" + avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/awm-relayer/config" + "github.com/ava-labs/awm-relayer/vms/vmtypes" + warpPayload "github.com/ava-labs/subnet-evm/warp/payload" + "go.uber.org/zap" +) + +type contractMessage struct { + logger logging.Logger +} + +func NewContractMessage(logger logging.Logger, subnetInfo config.SourceSubnet) *contractMessage { + return &contractMessage{ + logger: logger, + } +} + +func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.WarpMessageInfo, error) { + unsignedMsg, err := avalancheWarp.ParseUnsignedMessage(unsignedMsgBytes) + if err != nil { + m.logger.Error( + "Failed parsing unsigned message", + zap.Error(err), + ) + return nil, err + } + err = unsignedMsg.Initialize() + if err != nil { + m.logger.Error( + "Failed initializing unsigned message", + zap.Error(err), + ) + return nil, err + } + + warpPayload, err := warpPayload.ParseBlockHashPayload(unsignedMsg.Payload) + if err != nil { + m.logger.Error( + "Failed parsing addressed payload", + zap.Error(err), + ) + return nil, err + } + + messageInfo := vmtypes.WarpMessageInfo{ + WarpUnsignedMessage: unsignedMsg, + WarpPayload: warpPayload.BlockHash.Bytes(), + } + return &messageInfo, nil +} diff --git a/vms/evm_block_hash/subscriber.go b/vms/evm_block_hash/subscriber.go new file mode 100644 index 00000000..01e20c85 --- /dev/null +++ b/vms/evm_block_hash/subscriber.go @@ -0,0 +1,187 @@ +package evm_block_hash + +import ( + "context" + "errors" + "fmt" + "math/big" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" + avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/awm-relayer/config" + "github.com/ava-labs/awm-relayer/database" + "github.com/ava-labs/awm-relayer/vms/vmtypes" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/ethclient" + "github.com/ava-labs/subnet-evm/interfaces" + "github.com/ava-labs/subnet-evm/warp/payload" + "go.uber.org/zap" +) + +const ( + // Max buffer size for ethereum subscription channels + maxClientSubscriptionBuffer = 20000 + subscribeRetryTimeout = 1 * time.Second + maxResubscribeAttempts = 10 +) + +var ( + // Errors + ErrInvalidLog = errors.New("invalid warp block hash log") +) + +// subscriber implements Subscriber +type subscriber struct { + nodeWSURL string + nodeRPCURL string + chainID ids.ID + logsChan chan vmtypes.WarpLogInfo + blocks <-chan *types.Header + sub interfaces.Subscription + networkID uint32 + + logger logging.Logger + db database.RelayerDatabase +} + +// NewSubscriber returns a subscriber +func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db database.RelayerDatabase) *subscriber { + chainID, err := ids.FromString(subnetInfo.ChainID) + if err != nil { + logger.Error( + "Invalid chainID provided to subscriber", + zap.Error(err), + ) + return nil + } + + logs := make(chan vmtypes.WarpLogInfo, maxClientSubscriptionBuffer) + + return &subscriber{ + nodeWSURL: subnetInfo.GetNodeWSEndpoint(), + nodeRPCURL: subnetInfo.GetNodeRPCEndpoint(), + chainID: chainID, + logger: logger, + db: db, + logsChan: logs, + networkID: config.GetNetworkID(), + } +} + +func (s *subscriber) Subscribe() error { + // Retry subscribing until successful. Attempt to resubscribe maxResubscribeAttempts times + for attempt := 0; attempt < maxResubscribeAttempts; attempt++ { + // Unsubscribe before resubscribing + // s.sub should only be nil on the first call to Subscribe + if s.sub != nil { + s.sub.Unsubscribe() + } + err := s.dialAndSubscribe() + if err == nil { + s.logger.Info( + "Successfully subscribed", + zap.String("chainID", s.chainID.String()), + ) + return nil + } + + s.logger.Warn( + "Failed to subscribe to node", + zap.Int("attempt", attempt), + zap.String("chainID", s.chainID.String()), + zap.Error(err), + ) + + if attempt != maxResubscribeAttempts-1 { + time.Sleep(subscribeRetryTimeout) + } + } + + return fmt.Errorf("failed to subscribe to node with all %d attempts", maxResubscribeAttempts) +} + +func (s *subscriber) dialAndSubscribe() error { + // Dial the configured source chain endpoint + // This needs to be a websocket + ethClient, err := ethclient.Dial(s.nodeWSURL) + if err != nil { + return err + } + + blocks := make(chan *types.Header, maxClientSubscriptionBuffer) + sub, err := ethClient.SubscribeNewHead(context.Background(), blocks) + if err != nil { + s.logger.Error( + "Failed to subscribe to logs", + zap.String("chainID", s.chainID.String()), + zap.Error(err), + ) + return err + } + s.blocks = blocks + s.sub = sub + + // Forward logs to the interface channel. Closed when the subscription is cancelled + go s.forwardLogs() + return nil +} + +func (s *subscriber) NewWarpLogInfo(block *types.Header) (*vmtypes.WarpLogInfo, error) { + blockHashPayload, err := payload.NewBlockHashPayload(block.Hash()) + if err != nil { + return nil, err + } + unsignedMessage, err := avalancheWarp.NewUnsignedMessage(s.networkID, s.chainID, blockHashPayload.Bytes()) + if err != nil { + return nil, err + } + err = unsignedMessage.Initialize() + if err != nil { + return nil, err + } + + return &vmtypes.WarpLogInfo{ + UnsignedMsgBytes: unsignedMessage.Bytes(), + BlockNumber: block.Number.Uint64(), + }, nil +} + +// forward logs from the concrete log channel to the interface channel +func (s *subscriber) forwardLogs() { + for block := range s.blocks { + messageInfo, err := s.NewWarpLogInfo(block) + if err != nil { + s.logger.Error( + "Invalid log. Continuing.", + zap.Error(err), + ) + continue + } + s.logsChan <- *messageInfo + } +} + +func (s *subscriber) ProcessFromHeight(height *big.Int) error { + // TODO: Implement historical block processing + return nil +} + +func (s *subscriber) SetProcessedBlockHeightToLatest() error { + // TODO: Implement historical block processing + // We should distinguish the key from the value for the evm relayer: chainID_blockhash + return nil +} + +func (s *subscriber) Logs() <-chan vmtypes.WarpLogInfo { + return s.logsChan +} + +func (s *subscriber) Err() <-chan error { + return s.sub.Err() +} + +func (s *subscriber) Cancel() { + // Nothing to do here, the ethclient manages both the log and err channels +} diff --git a/vms/subscriber.go b/vms/subscriber.go index 95a80311..8c6b28f3 100644 --- a/vms/subscriber.go +++ b/vms/subscriber.go @@ -10,6 +10,7 @@ import ( "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/database" "github.com/ava-labs/awm-relayer/vms/evm" + "github.com/ava-labs/awm-relayer/vms/evm_block_hash" "github.com/ava-labs/awm-relayer/vms/vmtypes" ) @@ -39,12 +40,13 @@ type Subscriber interface { Cancel() } -// TODONOW: add evm_blockhash type // NewSubscriber returns a concrete Subscriber according to the VM specified by [subnetInfo] func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db database.RelayerDatabase) Subscriber { switch config.ParseVM(subnetInfo.VM) { case config.EVM: return evm.NewSubscriber(logger, subnetInfo, db) + case config.EVM_BLOCKHASH: + return evm_block_hash.NewSubscriber(logger, subnetInfo, db) default: return nil } From 4dc075aef8e22448d178cba5e20cb136f909e3b4 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 27 Sep 2023 19:34:39 +0000 Subject: [PATCH 04/26] wip --- messages/block_hash_publisher/config.go | 13 +++ messages/block_hash_publisher/config_test.go | 17 ++++ .../block_hash_publisher/message_manager.go | 83 +++++++++++++++++-- vms/evm_block_hash/subscriber.go | 1 + vms/vmtypes/message_info.go | 1 + vms/vmtypes/types.go | 1 + 6 files changed, 109 insertions(+), 7 deletions(-) diff --git a/messages/block_hash_publisher/config.go b/messages/block_hash_publisher/config.go index 33d08e18..467abe5f 100644 --- a/messages/block_hash_publisher/config.go +++ b/messages/block_hash_publisher/config.go @@ -1,6 +1,7 @@ package block_hash_publisher import ( + "encoding/hex" "fmt" "strconv" "strings" @@ -12,6 +13,7 @@ import ( type destinationInfo struct { ChainID string `json:"chain-id"` + Address string `json:"address"` Interval string `json:"interval"` useTimeInterval bool @@ -25,10 +27,21 @@ type Config struct { 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 subnetID in block hash publisher configuration. Provided ID: %s", destinationInfo.ChainID)) } + // Check if the address is valid + addr := destinationInfo.Address + if strings.HasPrefix(addr, "0x") { + addr = addr[2:] + } + _, 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)) + } + // Intervals must be either a positive integer, or a positive integer followed by "s" interval, isSeconds, err := parsePositiveIntWithSuffix(destinationInfo.Interval) if err != nil { diff --git a/messages/block_hash_publisher/config_test.go b/messages/block_hash_publisher/config_test.go index 0052fa29..f6548a9a 100644 --- a/messages/block_hash_publisher/config_test.go +++ b/messages/block_hash_publisher/config_test.go @@ -26,10 +26,12 @@ func TestConfigValidate(t *testing.T) { destinationChains: []destinationInfo{ { ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", Interval: "10", }, { ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", Interval: "10s", }, }, @@ -50,6 +52,7 @@ func TestConfigValidate(t *testing.T) { destinationChains: []destinationInfo{ { ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW7", + Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", Interval: "10", }, }, @@ -60,6 +63,7 @@ func TestConfigValidate(t *testing.T) { destinationChains: []destinationInfo{ { ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", Interval: "4r", }, }, @@ -70,6 +74,7 @@ func TestConfigValidate(t *testing.T) { destinationChains: []destinationInfo{ { ChainID: "9asUA3QckLh7vGnFQiiUJGPTx8KE4nFtP8c1wTWJuP8XiWW75", + Address: "0x50A46AA7b2eCBe2B1AbB7df865B9A87f5eed8635", Interval: "l", }, }, @@ -80,11 +85,23 @@ func TestConfigValidate(t *testing.T) { 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 { diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index 3bf6c09e..f1534fc5 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -2,6 +2,8 @@ package block_hash_publisher import ( "encoding/json" + "fmt" + "time" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" @@ -13,10 +15,21 @@ import ( "go.uber.org/zap" ) +const ( + publishBlockHashGasLimit = 100000 // TODONOW: set the correct gas limit +) + +type destinationSenderInfo struct { + client vms.DestinationClient + address common.Address + lastTimeSent time.Time + lastBlock uint64 +} + type messageManager struct { - messageConfig Config - destinationClients map[ids.ID]vms.DestinationClient - logger logging.Logger + messageConfig Config + destinations map[ids.ID]destinationSenderInfo + logger logging.Logger } func NewMessageManager( @@ -45,20 +58,76 @@ func NewMessageManager( return nil, err } + destinations := make(map[ids.ID]destinationSenderInfo) + for _, destination := range messageConfig.DestinationChains { + destinationID, err := ids.FromString(destination.ChainID) + if err != nil { + logger.Error( + "Failed to decode base-58 encoded destination chain ID", + zap.Error(err), + ) + return nil, err + } + destinations[destinationID] = destinationSenderInfo{ + address: common.HexToAddress(destination.Address), + client: destinationClients[destinationID], + } + } + return &messageManager{ - messageConfig: messageConfig, - destinationClients: destinationClients, - logger: logger, + messageConfig: messageConfig, + destinations: destinations, + logger: logger, }, nil } // ShouldSendMessage returns true if the message should be sent to the destination chain func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) { // TODONOW: check if the message should be sent to the destination chain based on the configured block/time interval + destination, ok := m.destinations[destinationChainID] + if !ok { + return false, fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID) + } + // TODO: config should be a map of destinationChainID -> config + // TODO: need to get the height+timestamp of the block with the hash + if m.messageConfig.DestinationChains[destinationChainID].useTimeInterval { + interval := m.messageConfig.DestinationChains[destinationChainID].timeIntervalSeconds + if time.Since(destination.lastTimeSent) < interval { + return false, nil + } + } else { + interval := m.messageConfig.DestinationChains[destinationChainID].blockInterval + if warpMessageInfo.BlockNumber-destination.lastBlock < uint64(interval) { + return false, nil + } + } return true, nil } func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { - // TODONOW: send the message to the destination chain + // TODONOW: Set the calldata by packing the ABI arguments + var callData []byte + + // Get the correct destination client from the global map + destination, ok := m.destinations[destinationChainID] + if !ok { + return fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID) + } + err := destination.client.SendTx(signedMessage, destination.address.Hex(), publishBlockHashGasLimit, callData) + if err != nil { + m.logger.Error( + "Failed to send tx.", + zap.String("destinationChainID", destinationChainID.String()), + zap.String("warpMessageID", signedMessage.ID().String()), + zap.Error(err), + ) + return err + } + // TODONOW: set the time/block number of the last sent message + m.logger.Info( + "Sent message to destination chain", + zap.String("destinationChainID", destinationChainID.String()), + zap.String("warpMessageID", signedMessage.ID().String()), + ) return nil } diff --git a/vms/evm_block_hash/subscriber.go b/vms/evm_block_hash/subscriber.go index 01e20c85..b0402afc 100644 --- a/vms/evm_block_hash/subscriber.go +++ b/vms/evm_block_hash/subscriber.go @@ -145,6 +145,7 @@ func (s *subscriber) NewWarpLogInfo(block *types.Header) (*vmtypes.WarpLogInfo, return &vmtypes.WarpLogInfo{ UnsignedMsgBytes: unsignedMessage.Bytes(), BlockNumber: block.Number.Uint64(), + BlockTimestamp: block.Time, }, nil } diff --git a/vms/vmtypes/message_info.go b/vms/vmtypes/message_info.go index b54da2b5..7bbecf45 100644 --- a/vms/vmtypes/message_info.go +++ b/vms/vmtypes/message_info.go @@ -18,4 +18,5 @@ type WarpLogInfo struct { SourceTxID []byte UnsignedMsgBytes []byte BlockNumber uint64 + BlockTimestamp uint64 } diff --git a/vms/vmtypes/types.go b/vms/vmtypes/types.go index c10ddf05..90caea52 100644 --- a/vms/vmtypes/types.go +++ b/vms/vmtypes/types.go @@ -12,6 +12,7 @@ import ( // // WarpMessageInfo is used internally to provide access to warp message info emitted by the sender +// TODO combine this with WarpLogInfo type WarpMessageInfo struct { WarpUnsignedMessage *warp.UnsignedMessage WarpPayload []byte From 92472142c4c4b3dfd8e945123abf331488f7b68f Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 27 Sep 2023 20:20:07 +0000 Subject: [PATCH 05/26] wip --- messages/block_hash_publisher/config.go | 13 +- .../block_hash_publisher/message_manager.go | 54 ++++---- .../message_manager_test.go | 120 ++++++++++++++++++ relayer/relayer.go | 12 +- vms/contract_message.go | 2 +- vms/evm/contract_message.go | 19 ++- vms/evm/subscriber.go | 14 +- vms/evm_block_hash/contract_message.go | 19 ++- vms/evm_block_hash/subscriber.go | 12 +- vms/subscriber.go | 2 +- vms/vmtypes/message_info.go | 21 +-- vms/vmtypes/types.go | 19 --- 12 files changed, 209 insertions(+), 98 deletions(-) create mode 100644 messages/block_hash_publisher/message_manager_test.go delete mode 100644 vms/vmtypes/types.go diff --git a/messages/block_hash_publisher/config.go b/messages/block_hash_publisher/config.go index 467abe5f..807ce185 100644 --- a/messages/block_hash_publisher/config.go +++ b/messages/block_hash_publisher/config.go @@ -5,7 +5,6 @@ import ( "fmt" "strconv" "strings" - "time" "github.com/ava-labs/avalanchego/ids" "github.com/pkg/errors" @@ -17,8 +16,8 @@ type destinationInfo struct { Interval string `json:"interval"` useTimeInterval bool - blockInterval int - timeIntervalSeconds time.Duration + blockInterval uint64 + timeIntervalSeconds uint64 } type Config struct { @@ -43,12 +42,12 @@ func (c *Config) Validate() error { } // Intervals must be either a positive integer, or a positive integer followed by "s" - interval, isSeconds, err := parsePositiveIntWithSuffix(destinationInfo.Interval) + 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 = time.Duration(interval) * time.Second + c.DestinationChains[i].timeIntervalSeconds = interval } else { c.DestinationChains[i].blockInterval = interval } @@ -57,7 +56,7 @@ func (c *Config) Validate() error { return nil } -func parsePositiveIntWithSuffix(input string) (int, bool, error) { +func parseIntervalWithSuffix(input string) (uint64, bool, error) { // Check if the input string is empty if input == "" { return 0, false, fmt.Errorf("empty string") @@ -79,5 +78,5 @@ func parsePositiveIntWithSuffix(input string) (int, bool, error) { return 0, false, err } - return intValue, hasSuffix, nil + return uint64(intValue), hasSuffix, nil } diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index f1534fc5..716a7a32 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -20,16 +20,22 @@ const ( ) type destinationSenderInfo struct { - client vms.DestinationClient - address common.Address - lastTimeSent time.Time - lastBlock uint64 + client vms.DestinationClient + address common.Address + + useTimeInterval bool + timeIntervalSeconds uint64 + blockInterval uint64 + + lastApprovedTime uint64 + lastTimeSent uint64 + lastApprovedBlock uint64 + lastBlock uint64 } type messageManager struct { - messageConfig Config - destinations map[ids.ID]destinationSenderInfo - logger logging.Logger + destinations map[ids.ID]*destinationSenderInfo + logger logging.Logger } func NewMessageManager( @@ -58,7 +64,7 @@ func NewMessageManager( return nil, err } - destinations := make(map[ids.ID]destinationSenderInfo) + destinations := make(map[ids.ID]*destinationSenderInfo) for _, destination := range messageConfig.DestinationChains { destinationID, err := ids.FromString(destination.ChainID) if err != nil { @@ -68,39 +74,40 @@ func NewMessageManager( ) return nil, err } - destinations[destinationID] = destinationSenderInfo{ - address: common.HexToAddress(destination.Address), - client: destinationClients[destinationID], + destinations[destinationID] = &destinationSenderInfo{ + useTimeInterval: destination.useTimeInterval, + timeIntervalSeconds: uint64(destination.timeIntervalSeconds), + address: common.HexToAddress(destination.Address), + client: destinationClients[destinationID], } } return &messageManager{ - messageConfig: messageConfig, - destinations: destinations, - logger: logger, + destinations: destinations, + logger: logger, }, nil } // ShouldSendMessage returns true if the message should be sent to the destination chain func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) { - // TODONOW: check if the message should be sent to the destination chain based on the configured block/time interval destination, ok := m.destinations[destinationChainID] if !ok { return false, fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID) } - // TODO: config should be a map of destinationChainID -> config - // TODO: need to get the height+timestamp of the block with the hash - if m.messageConfig.DestinationChains[destinationChainID].useTimeInterval { - interval := m.messageConfig.DestinationChains[destinationChainID].timeIntervalSeconds - if time.Since(destination.lastTimeSent) < interval { + if destination.useTimeInterval { + interval := destination.timeIntervalSeconds + if time.Unix(int64(warpMessageInfo.BlockTimestamp), 0).Sub(time.Unix(int64(destination.lastTimeSent), 0)) < (time.Duration(interval) * time.Second) { return false, nil } } else { - interval := m.messageConfig.DestinationChains[destinationChainID].blockInterval + interval := destination.blockInterval if warpMessageInfo.BlockNumber-destination.lastBlock < uint64(interval) { return false, nil } } + // Set the last approved block/time here. We don't set the last sent block/time until the message is actually sent + destination.lastApprovedBlock = warpMessageInfo.BlockNumber + destination.lastApprovedTime = warpMessageInfo.BlockTimestamp return true, nil } @@ -123,7 +130,10 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayloa ) return err } - // TODONOW: set the time/block number of the last sent message + + // Set the last sent block/time + destination.lastTimeSent = destination.lastApprovedTime + destination.lastBlock = destination.lastApprovedBlock m.logger.Info( "Sent message to destination chain", zap.String("destinationChainID", destinationChainID.String()), diff --git a/messages/block_hash_publisher/message_manager_test.go b/messages/block_hash_publisher/message_manager_test.go new file mode 100644 index 00000000..cc6407ca --- /dev/null +++ b/messages/block_hash_publisher/message_manager_test.go @@ -0,0 +1,120 @@ +package block_hash_publisher + +import ( + "testing" + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/awm-relayer/vms/vmtypes" + "github.com/stretchr/testify/require" +) + +func TestShouldSendMessage(t *testing.T) { + testCases := []struct { + name string + chainID ids.ID + destination destinationSenderInfo + warpMessageInfo vmtypes.WarpMessageInfo + expectedError bool + expectedResult bool + }{ + { + name: "should send (time)", + chainID: ids.GenerateTestID(), + destination: destinationSenderInfo{ + useTimeInterval: true, + timeIntervalSeconds: 10, + lastTimeSent: uint64(time.Unix(100, 0).Unix()), + }, + warpMessageInfo: vmtypes.WarpMessageInfo{ + BlockTimestamp: uint64(time.Unix(111, 0).Unix()), + }, + expectedError: false, + expectedResult: true, + }, + { + name: "should send (block)", + chainID: ids.GenerateTestID(), + destination: destinationSenderInfo{ + useTimeInterval: false, + blockInterval: 5, + lastBlock: 100, + }, + warpMessageInfo: vmtypes.WarpMessageInfo{ + BlockNumber: 106, + }, + expectedError: false, + expectedResult: true, + }, + { + name: "should send (time) 2", + chainID: ids.GenerateTestID(), + destination: destinationSenderInfo{ + useTimeInterval: true, + timeIntervalSeconds: 10, + lastTimeSent: uint64(time.Unix(100, 0).Unix()), + }, + warpMessageInfo: vmtypes.WarpMessageInfo{ + BlockTimestamp: uint64(time.Unix(110, 0).Unix()), + }, + expectedError: false, + expectedResult: true, + }, + { + name: "should send (block) 2", + chainID: ids.GenerateTestID(), + destination: destinationSenderInfo{ + useTimeInterval: false, + blockInterval: 5, + lastBlock: 100, + }, + warpMessageInfo: vmtypes.WarpMessageInfo{ + BlockNumber: 105, + }, + expectedError: false, + expectedResult: true, + }, + { + name: "should not send (time)", + chainID: ids.GenerateTestID(), + destination: destinationSenderInfo{ + useTimeInterval: true, + timeIntervalSeconds: 10, + lastTimeSent: uint64(time.Unix(100, 0).Unix()), + }, + warpMessageInfo: vmtypes.WarpMessageInfo{ + BlockTimestamp: uint64(time.Unix(109, 0).Unix()), + }, + expectedError: false, + expectedResult: false, + }, + { + name: "should not send (block)", + chainID: ids.GenerateTestID(), + destination: destinationSenderInfo{ + useTimeInterval: false, + blockInterval: 5, + lastBlock: 100, + }, + warpMessageInfo: vmtypes.WarpMessageInfo{ + BlockNumber: 104, + }, + expectedError: false, + expectedResult: false, + }, + } + for _, testCase := range testCases { + messageManager := &messageManager{ + destinations: map[ids.ID]*destinationSenderInfo{ + testCase.chainID: &testCase.destination, + }, + } + result, err := messageManager.ShouldSendMessage(&testCase.warpMessageInfo, testCase.chainID) + if testCase.expectedError { + require.Error(t, err, testCase.name) + } else { + require.NoError(t, err, testCase.name) + require.Equal(t, testCase.expectedResult, result, testCase.name) + } + } +} diff --git a/relayer/relayer.go b/relayer/relayer.go index 4fac74c9..72a9a73c 100644 --- a/relayer/relayer.go +++ b/relayer/relayer.go @@ -172,14 +172,14 @@ func NewRelayer( } // RelayMessage relays a single warp message to the destination chain. Warp message relay requests from the same origin chain are processed serially -func (r *Relayer) RelayMessage(warpLogInfo *vmtypes.WarpLogInfo, metrics *MessageRelayerMetrics, messageCreator message.Creator) error { +func (r *Relayer) RelayMessage(warpMessageInfo *vmtypes.WarpMessageInfo, metrics *MessageRelayerMetrics, messageCreator message.Creator) error { r.logger.Info( "Relaying message", zap.String("chainID", r.sourceChainID.String()), ) // Unpack the VM message bytes into a Warp message - warpMessageInfo, err := r.contractMessage.UnpackWarpMessage(warpLogInfo.UnsignedMsgBytes) + err := r.contractMessage.UnpackWarpMessage(warpMessageInfo) if err != nil { r.logger.Error( "Failed to unpack sender message", @@ -195,19 +195,19 @@ func (r *Relayer) RelayMessage(warpLogInfo *vmtypes.WarpLogInfo, metrics *Messag ) // Check that the warp message is from a support message protocol contract address. - messageManager, supportedMessageProtocol := r.messageManagers[warpLogInfo.SourceAddress] + messageManager, supportedMessageProtocol := r.messageManagers[warpMessageInfo.SourceAddress] if !supportedMessageProtocol { // Do not return an error here because it is expected for there to be messages from other contracts // than just the ones supported by a single relayer instance. r.logger.Debug( "Warp message from unsupported message protocol address. Not relaying.", - zap.String("protocolAddress", warpLogInfo.SourceAddress.Hex()), + zap.String("protocolAddress", warpMessageInfo.SourceAddress.Hex()), ) return nil } // Create and run the message relayer to attempt to deliver the message to the destination chain - messageRelayer := newMessageRelayer(r.logger, metrics, r, warpMessageInfo, warpLogInfo.DestinationChainID, r.responseChan, messageCreator) + messageRelayer := newMessageRelayer(r.logger, metrics, r, warpMessageInfo, warpMessageInfo.DestinationChainID, r.responseChan, messageCreator) if err != nil { r.logger.Error( "Failed to create message relayer", @@ -233,7 +233,7 @@ func (r *Relayer) RelayMessage(warpLogInfo *vmtypes.WarpLogInfo, metrics *Messag r.currentRequestID++ // Update the database with the latest processed block height - err = r.db.Put(r.sourceChainID, []byte(database.LatestProcessedBlockKey), []byte(strconv.FormatUint(warpLogInfo.BlockNumber, 10))) + err = r.db.Put(r.sourceChainID, []byte(database.LatestProcessedBlockKey), []byte(strconv.FormatUint(warpMessageInfo.BlockNumber, 10))) if err != nil { r.logger.Error( fmt.Sprintf("failed to put %s into database", database.LatestProcessedBlockKey), diff --git a/vms/contract_message.go b/vms/contract_message.go index 4b032258..160bc72a 100644 --- a/vms/contract_message.go +++ b/vms/contract_message.go @@ -13,7 +13,7 @@ import ( type ContractMessage interface { // UnpackWarpMessage unpacks the warp message from the VM - UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.WarpMessageInfo, error) + UnpackWarpMessage(warpMessageInfo *vmtypes.WarpMessageInfo) error } func NewContractMessage(logger logging.Logger, subnetInfo config.SourceSubnet) ContractMessage { diff --git a/vms/evm/contract_message.go b/vms/evm/contract_message.go index d4441352..c9ed95c3 100644 --- a/vms/evm/contract_message.go +++ b/vms/evm/contract_message.go @@ -22,14 +22,14 @@ func NewContractMessage(logger logging.Logger, subnetInfo config.SourceSubnet) * } } -func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.WarpMessageInfo, error) { - unsignedMsg, err := avalancheWarp.ParseUnsignedMessage(unsignedMsgBytes) +func (m *contractMessage) UnpackWarpMessage(warpMessageInfo *vmtypes.WarpMessageInfo) error { + unsignedMsg, err := avalancheWarp.ParseUnsignedMessage(warpMessageInfo.UnsignedMsgBytes) if err != nil { m.logger.Error( "Failed parsing unsigned message", zap.Error(err), ) - return nil, err + return err } err = unsignedMsg.Initialize() if err != nil { @@ -37,7 +37,7 @@ func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.W "Failed initializing unsigned message", zap.Error(err), ) - return nil, err + return err } warpPayload, err := warpPayload.ParseAddressedPayload(unsignedMsg.Payload) @@ -46,12 +46,11 @@ func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.W "Failed parsing addressed payload", zap.Error(err), ) - return nil, err + return err } - messageInfo := vmtypes.WarpMessageInfo{ - WarpUnsignedMessage: unsignedMsg, - WarpPayload: warpPayload.Payload, - } - return &messageInfo, nil + warpMessageInfo.WarpUnsignedMessage = unsignedMsg + warpMessageInfo.WarpPayload = warpPayload.Payload + + return nil } diff --git a/vms/evm/subscriber.go b/vms/evm/subscriber.go index c42aac03..851da74d 100644 --- a/vms/evm/subscriber.go +++ b/vms/evm/subscriber.go @@ -55,7 +55,7 @@ type subscriber struct { nodeWSURL string nodeRPCURL string chainID ids.ID - logsChan chan vmtypes.WarpLogInfo + logsChan chan vmtypes.WarpMessageInfo evmLog <-chan types.Log sub interfaces.Subscription @@ -74,7 +74,7 @@ func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db dat return nil } - logs := make(chan vmtypes.WarpLogInfo, maxClientSubscriptionBuffer) + logs := make(chan vmtypes.WarpMessageInfo, maxClientSubscriptionBuffer) return &subscriber{ nodeWSURL: subnetInfo.GetNodeWSEndpoint(), @@ -86,7 +86,7 @@ func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db dat } } -func (s *subscriber) NewWarpLogInfo(log types.Log) (*vmtypes.WarpLogInfo, error) { +func (s *subscriber) NewWarpMessageInfo(log types.Log) (*vmtypes.WarpMessageInfo, error) { if len(log.Topics) != 4 { s.logger.Error( "Log did not have the correct number of topics", @@ -111,7 +111,7 @@ func (s *subscriber) NewWarpLogInfo(log types.Log) (*vmtypes.WarpLogInfo, error) return nil, ErrInvalidLog } - return &vmtypes.WarpLogInfo{ + return &vmtypes.WarpMessageInfo{ DestinationChainID: destinationChainID, DestinationAddress: log.Topics[2], SourceAddress: log.Topics[3], @@ -124,7 +124,7 @@ func (s *subscriber) NewWarpLogInfo(log types.Log) (*vmtypes.WarpLogInfo, error) // forward logs from the concrete log channel to the interface channel func (s *subscriber) forwardLogs() { for msgLog := range s.evmLog { - messageInfo, err := s.NewWarpLogInfo(msgLog) + messageInfo, err := s.NewWarpMessageInfo(msgLog) if err != nil { s.logger.Error( "Invalid log. Continuing.", @@ -211,7 +211,7 @@ func (s *subscriber) ProcessFromHeight(height *big.Int) error { zap.String("chainID", s.chainID.String()), ) for _, log := range logs { - messageInfo, err := s.NewWarpLogInfo(log) + messageInfo, err := s.NewWarpMessageInfo(log) if err != nil { s.logger.Error( "Invalid log when processing from height. Continuing.", @@ -321,7 +321,7 @@ func (s *subscriber) dialAndSubscribe() error { return nil } -func (s *subscriber) Logs() <-chan vmtypes.WarpLogInfo { +func (s *subscriber) Logs() <-chan vmtypes.WarpMessageInfo { return s.logsChan } diff --git a/vms/evm_block_hash/contract_message.go b/vms/evm_block_hash/contract_message.go index d6319808..eb86bc44 100644 --- a/vms/evm_block_hash/contract_message.go +++ b/vms/evm_block_hash/contract_message.go @@ -19,14 +19,14 @@ func NewContractMessage(logger logging.Logger, subnetInfo config.SourceSubnet) * } } -func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.WarpMessageInfo, error) { - unsignedMsg, err := avalancheWarp.ParseUnsignedMessage(unsignedMsgBytes) +func (m *contractMessage) UnpackWarpMessage(warpMessageInfo *vmtypes.WarpMessageInfo) error { + unsignedMsg, err := avalancheWarp.ParseUnsignedMessage(warpMessageInfo.UnsignedMsgBytes) if err != nil { m.logger.Error( "Failed parsing unsigned message", zap.Error(err), ) - return nil, err + return err } err = unsignedMsg.Initialize() if err != nil { @@ -34,7 +34,7 @@ func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.W "Failed initializing unsigned message", zap.Error(err), ) - return nil, err + return err } warpPayload, err := warpPayload.ParseBlockHashPayload(unsignedMsg.Payload) @@ -43,12 +43,11 @@ func (m *contractMessage) UnpackWarpMessage(unsignedMsgBytes []byte) (*vmtypes.W "Failed parsing addressed payload", zap.Error(err), ) - return nil, err + return err } - messageInfo := vmtypes.WarpMessageInfo{ - WarpUnsignedMessage: unsignedMsg, - WarpPayload: warpPayload.BlockHash.Bytes(), - } - return &messageInfo, nil + warpMessageInfo.WarpUnsignedMessage = unsignedMsg + warpMessageInfo.WarpPayload = warpPayload.BlockHash.Bytes() + + return nil } diff --git a/vms/evm_block_hash/subscriber.go b/vms/evm_block_hash/subscriber.go index b0402afc..96dff49e 100644 --- a/vms/evm_block_hash/subscriber.go +++ b/vms/evm_block_hash/subscriber.go @@ -37,7 +37,7 @@ type subscriber struct { nodeWSURL string nodeRPCURL string chainID ids.ID - logsChan chan vmtypes.WarpLogInfo + logsChan chan vmtypes.WarpMessageInfo blocks <-chan *types.Header sub interfaces.Subscription networkID uint32 @@ -57,7 +57,7 @@ func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db dat return nil } - logs := make(chan vmtypes.WarpLogInfo, maxClientSubscriptionBuffer) + logs := make(chan vmtypes.WarpMessageInfo, maxClientSubscriptionBuffer) return &subscriber{ nodeWSURL: subnetInfo.GetNodeWSEndpoint(), @@ -128,7 +128,7 @@ func (s *subscriber) dialAndSubscribe() error { return nil } -func (s *subscriber) NewWarpLogInfo(block *types.Header) (*vmtypes.WarpLogInfo, error) { +func (s *subscriber) NewWarpMessageInfo(block *types.Header) (*vmtypes.WarpMessageInfo, error) { blockHashPayload, err := payload.NewBlockHashPayload(block.Hash()) if err != nil { return nil, err @@ -142,7 +142,7 @@ func (s *subscriber) NewWarpLogInfo(block *types.Header) (*vmtypes.WarpLogInfo, return nil, err } - return &vmtypes.WarpLogInfo{ + return &vmtypes.WarpMessageInfo{ UnsignedMsgBytes: unsignedMessage.Bytes(), BlockNumber: block.Number.Uint64(), BlockTimestamp: block.Time, @@ -152,7 +152,7 @@ func (s *subscriber) NewWarpLogInfo(block *types.Header) (*vmtypes.WarpLogInfo, // forward logs from the concrete log channel to the interface channel func (s *subscriber) forwardLogs() { for block := range s.blocks { - messageInfo, err := s.NewWarpLogInfo(block) + messageInfo, err := s.NewWarpMessageInfo(block) if err != nil { s.logger.Error( "Invalid log. Continuing.", @@ -175,7 +175,7 @@ func (s *subscriber) SetProcessedBlockHeightToLatest() error { return nil } -func (s *subscriber) Logs() <-chan vmtypes.WarpLogInfo { +func (s *subscriber) Logs() <-chan vmtypes.WarpMessageInfo { return s.logsChan } diff --git a/vms/subscriber.go b/vms/subscriber.go index 8c6b28f3..5bb34eb3 100644 --- a/vms/subscriber.go +++ b/vms/subscriber.go @@ -30,7 +30,7 @@ type Subscriber interface { Subscribe() error // Logs returns the channel that the subscription writes events to - Logs() <-chan vmtypes.WarpLogInfo + Logs() <-chan vmtypes.WarpMessageInfo // Err returns the channel that the subscription writes errors to // If an error is sent to this channel, the subscription should be closed diff --git a/vms/vmtypes/message_info.go b/vms/vmtypes/message_info.go index 7bbecf45..fd346f82 100644 --- a/vms/vmtypes/message_info.go +++ b/vms/vmtypes/message_info.go @@ -5,18 +5,21 @@ package vmtypes import ( "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ethereum/go-ethereum/common" ) -// WarpLogInfo is provided by the subscription and +// WarpMessageInfo is provided by the subscription and // describes the transaction information emitted by the source chain, // including the Warp Message payload bytes -type WarpLogInfo struct { - SourceAddress common.Hash - DestinationChainID ids.ID - DestinationAddress common.Hash - SourceTxID []byte - UnsignedMsgBytes []byte - BlockNumber uint64 - BlockTimestamp uint64 +type WarpMessageInfo struct { + SourceAddress common.Hash + DestinationChainID ids.ID + DestinationAddress common.Hash + SourceTxID []byte + UnsignedMsgBytes []byte + BlockNumber uint64 + BlockTimestamp uint64 + WarpUnsignedMessage *warp.UnsignedMessage + WarpPayload []byte } diff --git a/vms/vmtypes/types.go b/vms/vmtypes/types.go deleted file mode 100644 index 90caea52..00000000 --- a/vms/vmtypes/types.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package vmtypes - -import ( - "github.com/ava-labs/avalanchego/vms/platformvm/warp" -) - -// -// Types used in the vms interfaces -// - -// WarpMessageInfo is used internally to provide access to warp message info emitted by the sender -// TODO combine this with WarpLogInfo -type WarpMessageInfo struct { - WarpUnsignedMessage *warp.UnsignedMessage - WarpPayload []byte -} From 4e602dcfee2112e018c30c6c706acd36aa265aeb Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 28 Sep 2023 14:41:44 +0000 Subject: [PATCH 06/26] pack receive block hash --- go.mod | 1 + go.sum | 4 +++ .../block_hash_publisher/message_manager.go | 26 ++++++++++++++++--- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 08fd3777..30e5b15b 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( require ( github.com/Microsoft/go-winio v0.5.2 // indirect github.com/ava-labs/coreth v0.12.5-rc.6 // indirect + github.com/ava-labs/teleporter v0.0.0-20230928142515-43ad4b3620af // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect diff --git a/go.sum b/go.sum index 08b32a2c..74e12c66 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,10 @@ 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-20230927213446-c08725fa0c66 h1:x0sfz1yv4wv6u33usI+rDbGND/Rc2cbw5D+MvcOAD8c= +github.com/ava-labs/teleporter v0.0.0-20230927213446-c08725fa0c66/go.mod h1:ULmbg6e2iiTuj2ac9Ru53sV2ngAsX6csZSKesIPE6lU= +github.com/ava-labs/teleporter v0.0.0-20230928142515-43ad4b3620af h1:OazJMJ2aMkOVuj6MfRHZ3d/n2RbedMzMo6nGHHoj09k= +github.com/ava-labs/teleporter v0.0.0-20230928142515-43ad4b3620af/go.mod h1:ULmbg6e2iiTuj2ac9Ru53sV2ngAsX6csZSKesIPE6lU= 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= diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index 716a7a32..f79030fc 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/vms" "github.com/ava-labs/awm-relayer/vms/vmtypes" + teleporter_block_hash "github.com/ava-labs/teleporter/abis/go/teleporter-block-hash" "github.com/ethereum/go-ethereum/common" "go.uber.org/zap" ) @@ -112,15 +113,34 @@ func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageI } func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { - // TODONOW: Set the calldata by packing the ABI arguments - var callData []byte + m.logger.Info( + "Sending message to destination chain", + zap.String("destinationChainID", destinationChainID.String()), + zap.String("warpMessageID", signedMessage.ID().String()), + ) + // Construct the transaction call data to call the receive cross chain message method of the receiver precompile. + callData, err := teleporter_block_hash.PackReceiveBlockHash(teleporter_block_hash.ReceiveBlockHashInput{ + MessageIndex: uint32(0), + SourceChainID: signedMessage.SourceChainID, + }) + if err != nil { + m.logger.Error( + "Failed packing receiveCrossChainMessage call data", + zap.Error(err), + zap.Uint32("messageIndex", 0), + zap.String("sourceChainID", signedMessage.SourceChainID.String()), + zap.String("destinationChainID", destinationChainID.String()), + zap.String("warpMessageID", signedMessage.ID().String()), + ) + return err + } // Get the correct destination client from the global map destination, ok := m.destinations[destinationChainID] if !ok { return fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID) } - err := destination.client.SendTx(signedMessage, destination.address.Hex(), publishBlockHashGasLimit, callData) + err = destination.client.SendTx(signedMessage, destination.address.Hex(), publishBlockHashGasLimit, callData) if err != nil { m.logger.Error( "Failed to send tx.", From 02ee06ba17773f6bda99b4e3306404dd21191d93 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Fri, 29 Sep 2023 22:10:14 +0000 Subject: [PATCH 07/26] wip: receiving block hash log --- config/config.go | 3 + main/main.go | 2 +- messages/block_hash_publisher/config.go | 5 +- .../block_hash_publisher/message_manager.go | 29 +- tests/BlockHashReceiverByteCode.txt | 1 + tests/e2e_test.go | 772 ++++++++++++------ tests/utils.go | 18 + vms/evm_block_hash/subscriber.go | 100 ++- 8 files changed, 668 insertions(+), 262 deletions(-) create mode 100644 tests/BlockHashReceiverByteCode.txt diff --git a/config/config.go b/config/config.go index 5210ab78..01b3a547 100644 --- a/config/config.go +++ b/config/config.go @@ -237,6 +237,9 @@ func (s *SourceSubnet) Validate() error { return fmt.Errorf("invalid message contract address in EVM source subnet: %s", messageContractAddress) } } + case EVM_BLOCKHASH: + // No additional validation required + // TODONOW: we shouldn't require an address as the key for block hash publisher default: return fmt.Errorf("unsupported VM type for source subnet: %v", s.VM) } diff --git a/main/main.go b/main/main.go index c9ef56d5..333c3603 100644 --- a/main/main.go +++ b/main/main.go @@ -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()), diff --git a/messages/block_hash_publisher/config.go b/messages/block_hash_publisher/config.go index 807ce185..8a097f20 100644 --- a/messages/block_hash_publisher/config.go +++ b/messages/block_hash_publisher/config.go @@ -28,11 +28,14 @@ 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 subnetID in block hash publisher configuration. Provided ID: %s", destinationInfo.ChainID)) + 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") + } if strings.HasPrefix(addr, "0x") { addr = addr[2:] } diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index f79030fc..a77199ff 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -17,7 +17,7 @@ import ( ) const ( - publishBlockHashGasLimit = 100000 // TODONOW: set the correct gas limit + publishBlockHashGasLimit = 1000000 // TODONOW: set the correct gas limit ) type destinationSenderInfo struct { @@ -45,7 +45,7 @@ func NewMessageManager( messageProtocolConfig config.MessageProtocolConfig, destinationClients map[ids.ID]vms.DestinationClient, ) (*messageManager, error) { - // Marshal the map and unmarshal into the Teleporter config + // Marshal the map and unmarshal into the Block Hash Publisher config data, err := json.Marshal(messageProtocolConfig.Settings) if err != nil { logger.Error("Failed to marshal Block Hash Publisher config") @@ -64,6 +64,7 @@ func NewMessageManager( ) return nil, err } + logger.Info("DEBUG CONFIG", zap.String("config", fmt.Sprintf("%#v", messageConfig))) destinations := make(map[ids.ID]*destinationSenderInfo) for _, destination := range messageConfig.DestinationChains { @@ -78,9 +79,12 @@ func NewMessageManager( destinations[destinationID] = &destinationSenderInfo{ useTimeInterval: destination.useTimeInterval, timeIntervalSeconds: uint64(destination.timeIntervalSeconds), + blockInterval: uint64(destination.blockInterval), address: common.HexToAddress(destination.Address), client: destinationClients[destinationID], } + logger.Info("DEBUG DESTINATIONS", zap.String("address", destinations[destinationID].address.String())) + } return &messageManager{ @@ -93,6 +97,15 @@ func NewMessageManager( func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) { destination, ok := m.destinations[destinationChainID] if !ok { + var destinationIDs []string + for id := range m.destinations { + destinationIDs = append(destinationIDs, id.String()) + } + m.logger.Info( + "DEBUG", + zap.String("destinationChainID", destinationChainID.String()), + zap.String("configuredDestinations", fmt.Sprintf("%#v", destinationIDs)), + ) return false, fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID) } if destination.useTimeInterval { @@ -103,12 +116,24 @@ func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageI } else { interval := destination.blockInterval if warpMessageInfo.BlockNumber-destination.lastBlock < uint64(interval) { + m.logger.Info( + "DEBUG", + zap.String("decision", "Not sending"), + zap.Int("blockNum", int(warpMessageInfo.BlockNumber)), + zap.Int("lastBlockNum", int(destination.lastBlock)), + ) return false, nil } } // Set the last approved block/time here. We don't set the last sent block/time until the message is actually sent destination.lastApprovedBlock = warpMessageInfo.BlockNumber destination.lastApprovedTime = warpMessageInfo.BlockTimestamp + m.logger.Info( + "DEBUG", + zap.String("decision", "Sending"), + zap.Int("blockNum", int(warpMessageInfo.BlockNumber)), + zap.Int("lastBlockNum", int(destination.lastBlock)), + ) return true, nil } diff --git a/tests/BlockHashReceiverByteCode.txt b/tests/BlockHashReceiverByteCode.txt new file mode 100644 index 00000000..efaf55db --- /dev/null +++ b/tests/BlockHashReceiverByteCode.txt @@ -0,0 +1 @@ +0x608060405234801561001057600080fd5b50600160008190558055610280806100296000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806330b525591461003b578063b771b3bc14610050575b600080fd5b61004e61004936600461018f565b61007a565b005b61005e6005600160991b0181565b6040516001600160a01b03909116815260200160405180910390f35b600180541461009c5760405163a815ca6b60e01b815260040160405180910390fd5b600260015560405163ce7f592960e01b815263ffffffff8316600482015260009081906005600160991b019063ce7f592990602401606060405180830381865afa1580156100ee573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061011291906101c4565b9150915080610134576040516339ecf43d60e21b815260040160405180910390fd5b815183146101555760405163e23ef14d60e01b815260040160405180910390fd5b602082015182516040517f7770e5f72465e9b05c8076c3f2eac70898abe6a84f0259307d127c13e2a1a4e490600090a35050600180555050565b600080604083850312156101a257600080fd5b823563ffffffff811681146101b657600080fd5b946020939093013593505050565b60008082840360608112156101d857600080fd5b60408112156101e657600080fd5b506040516040810181811067ffffffffffffffff8211171561021857634e487b7160e01b600052604160045260246000fd5b60409081528451825260208086015190830152840151909250801515811461023f57600080fd5b80915050925092905056fea264697066735822122035befb02f079d9bed5ae8edf23154d8ee3b487f193f6962a513df22f76fd6b6464736f6c63430008120033 \ No newline at end of file diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 4ba2a9b4..86aa17c8 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -4,8 +4,10 @@ package tests import ( + "bufio" "context" "crypto/ecdsa" + "encoding/hex" "encoding/json" "fmt" "math/big" @@ -17,21 +19,19 @@ import ( "github.com/ava-labs/avalanche-network-runner/rpcpb" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/awm-relayer/config" - "github.com/ava-labs/awm-relayer/database" "github.com/ava-labs/awm-relayer/messages/teleporter" "github.com/ava-labs/awm-relayer/peers" relayerEvm "github.com/ava-labs/awm-relayer/vms/evm" + "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" + "github.com/ava-labs/subnet-evm/interfaces" "github.com/ava-labs/subnet-evm/plugin/evm" - "github.com/ava-labs/subnet-evm/rpc" "github.com/ava-labs/subnet-evm/tests/utils/runner" - predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" - warpPayload "github.com/ava-labs/subnet-evm/warp/payload" - "github.com/ava-labs/subnet-evm/x/warp" + teleporter_block_hash "github.com/ava-labs/teleporter/abis/go/teleporter-block-hash" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/onsi/ginkgo/v2" @@ -63,7 +63,7 @@ var ( subnetIDs []ids.ID subnetA, subnetB ids.ID fundedKey *ecdsa.PrivateKey - chainBWSClient ethclient.Client + chainAWSClient, chainBWSClient ethclient.Client chainARPCClient, chainBRPCClient ethclient.Client chainARPCURI, chainBRPCURI string chainAIDInt, chainBIDInt *big.Int @@ -167,6 +167,8 @@ var _ = ginkgo.BeforeSuite(func() { chainAWSURI := httpToWebsocketURI(chainANodeURIs[0], blockchainIDA.String()) chainARPCURI = httpToRPCURI(chainANodeURIs[0], blockchainIDA.String()) log.Info("Creating ethclient for blockchainA", "wsURI", chainAWSURI, "rpcURL, chainARPCURI") + chainAWSClient, err = ethclient.Dial(chainAWSURI) + Expect(err).Should(BeNil()) chainARPCClient, err = ethclient.Dial(chainARPCURI) Expect(err).Should(BeNil()) @@ -185,98 +187,98 @@ var _ = ginkgo.BeforeSuite(func() { Expect(err).Should(BeNil()) log.Info("Finished setting up e2e test subnet variables") - log.Info("Deploying Teleporter contract to subnets") - // Read in the Teleporter contract information - teleporterContractAddress = common.HexToAddress(readHexTextFile("./tests/UniversalTeleporterMessengerContractAddress.txt")) - teleporterDeployerAddress := common.HexToAddress(readHexTextFile("./tests/UniversalTeleporterDeployerAddress.txt")) - teleporterDeployerTransaction := readHexTextFile("./tests/UniversalTeleporterDeployerTransaction.txt") - - nonceA, err := chainARPCClient.NonceAt(ctx, fundedAddress, nil) - Expect(err).Should(BeNil()) - - nonceB, err := chainBRPCClient.NonceAt(ctx, fundedAddress, nil) - Expect(err).Should(BeNil()) - - gasTipCapA, err := chainARPCClient.SuggestGasTipCap(context.Background()) - Expect(err).Should(BeNil()) - gasTipCapB, err := chainBRPCClient.SuggestGasTipCap(context.Background()) - Expect(err).Should(BeNil()) - - baseFeeA, err := chainARPCClient.EstimateBaseFee(context.Background()) - Expect(err).Should(BeNil()) - gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) - gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) - - baseFeeB, err := chainBRPCClient.EstimateBaseFee(context.Background()) - Expect(err).Should(BeNil()) - gasFeeCapB := baseFeeB.Mul(baseFeeB, big.NewInt(relayerEvm.BaseFeeFactor)) - gasFeeCapB.Add(gasFeeCapB, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) - - // Fund the deployer address - { - value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth - txA := types.NewTx(&types.DynamicFeeTx{ - ChainID: chainAIDInt, - Nonce: nonceA, - To: &teleporterDeployerAddress, - Gas: defaultTeleporterMessageGas, - GasFeeCap: gasFeeCapA, - GasTipCap: gasTipCapA, - Value: value, - }) - txSignerA := types.LatestSignerForChainID(chainAIDInt) - triggerTxA, err := types.SignTx(txA, txSignerA, fundedKey) - Expect(err).Should(BeNil()) - err = chainARPCClient.SendTransaction(ctx, triggerTxA) - Expect(err).Should(BeNil()) - time.Sleep(5 * time.Second) - receipt, err := chainARPCClient.TransactionReceipt(ctx, triggerTxA.Hash()) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - } - { - value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth - txB := types.NewTx(&types.DynamicFeeTx{ - ChainID: chainBIDInt, - Nonce: nonceB, - To: &teleporterDeployerAddress, - Gas: defaultTeleporterMessageGas, - GasFeeCap: gasFeeCapB, - GasTipCap: gasTipCapB, - Value: value, - }) - txSignerB := types.LatestSignerForChainID(chainBIDInt) - triggerTxB, err := types.SignTx(txB, txSignerB, fundedKey) - Expect(err).Should(BeNil()) - err = chainBRPCClient.SendTransaction(ctx, triggerTxB) - Expect(err).Should(BeNil()) - time.Sleep(5 * time.Second) - receipt, err := chainBRPCClient.TransactionReceipt(ctx, triggerTxB.Hash()) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - } - // Deploy Teleporter on the two subnets - { - rpcClient, err := rpc.DialContext(ctx, chainARPCURI) - Expect(err).Should(BeNil()) - err = rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", teleporterDeployerTransaction) - Expect(err).Should(BeNil()) - time.Sleep(5 * time.Second) - teleporterCode, err := chainARPCClient.CodeAt(ctx, teleporterContractAddress, nil) - Expect(err).Should(BeNil()) - Expect(len(teleporterCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode - } - { - rpcClient, err := rpc.DialContext(ctx, chainBRPCURI) - Expect(err).Should(BeNil()) - err = rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", teleporterDeployerTransaction) - Expect(err).Should(BeNil()) - time.Sleep(5 * time.Second) - teleporterCode, err := chainBRPCClient.CodeAt(ctx, teleporterContractAddress, nil) - Expect(err).Should(BeNil()) - Expect(len(teleporterCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode - } - log.Info("Finished deploying Teleporter contracts") + // log.Info("Deploying Teleporter contract to subnets") + // // Read in the Teleporter contract information + // teleporterContractAddress = common.HexToAddress(readHexTextFile("./tests/UniversalTeleporterMessengerContractAddress.txt")) + // teleporterDeployerAddress := common.HexToAddress(readHexTextFile("./tests/UniversalTeleporterDeployerAddress.txt")) + // teleporterDeployerTransaction := readHexTextFile("./tests/UniversalTeleporterDeployerTransaction.txt") + + // nonceA, err := chainARPCClient.NonceAt(ctx, fundedAddress, nil) + // Expect(err).Should(BeNil()) + + // nonceB, err := chainBRPCClient.NonceAt(ctx, fundedAddress, nil) + // Expect(err).Should(BeNil()) + + // gasTipCapA, err := chainARPCClient.SuggestGasTipCap(context.Background()) + // Expect(err).Should(BeNil()) + // gasTipCapB, err := chainBRPCClient.SuggestGasTipCap(context.Background()) + // Expect(err).Should(BeNil()) + + // baseFeeA, err := chainARPCClient.EstimateBaseFee(context.Background()) + // Expect(err).Should(BeNil()) + // gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) + // gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) + + // baseFeeB, err := chainBRPCClient.EstimateBaseFee(context.Background()) + // Expect(err).Should(BeNil()) + // gasFeeCapB := baseFeeB.Mul(baseFeeB, big.NewInt(relayerEvm.BaseFeeFactor)) + // gasFeeCapB.Add(gasFeeCapB, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) + + // // Fund the deployer address + // { + // value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth + // txA := types.NewTx(&types.DynamicFeeTx{ + // ChainID: chainAIDInt, + // Nonce: nonceA, + // To: &teleporterDeployerAddress, + // Gas: defaultTeleporterMessageGas, + // GasFeeCap: gasFeeCapA, + // GasTipCap: gasTipCapA, + // Value: value, + // }) + // txSignerA := types.LatestSignerForChainID(chainAIDInt) + // triggerTxA, err := types.SignTx(txA, txSignerA, fundedKey) + // Expect(err).Should(BeNil()) + // err = chainARPCClient.SendTransaction(ctx, triggerTxA) + // Expect(err).Should(BeNil()) + // time.Sleep(5 * time.Second) + // receipt, err := chainARPCClient.TransactionReceipt(ctx, triggerTxA.Hash()) + // Expect(err).Should(BeNil()) + // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + // } + // { + // value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(10)) // 10eth + // txB := types.NewTx(&types.DynamicFeeTx{ + // ChainID: chainBIDInt, + // Nonce: nonceB, + // To: &teleporterDeployerAddress, + // Gas: defaultTeleporterMessageGas, + // GasFeeCap: gasFeeCapB, + // GasTipCap: gasTipCapB, + // Value: value, + // }) + // txSignerB := types.LatestSignerForChainID(chainBIDInt) + // triggerTxB, err := types.SignTx(txB, txSignerB, fundedKey) + // Expect(err).Should(BeNil()) + // err = chainBRPCClient.SendTransaction(ctx, triggerTxB) + // Expect(err).Should(BeNil()) + // time.Sleep(5 * time.Second) + // receipt, err := chainBRPCClient.TransactionReceipt(ctx, triggerTxB.Hash()) + // Expect(err).Should(BeNil()) + // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + // } + // // Deploy Teleporter on the two subnets + // { + // rpcClient, err := rpc.DialContext(ctx, chainARPCURI) + // Expect(err).Should(BeNil()) + // err = rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", teleporterDeployerTransaction) + // Expect(err).Should(BeNil()) + // time.Sleep(5 * time.Second) + // teleporterCode, err := chainARPCClient.CodeAt(ctx, teleporterContractAddress, nil) + // Expect(err).Should(BeNil()) + // Expect(len(teleporterCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode + // } + // { + // rpcClient, err := rpc.DialContext(ctx, chainBRPCURI) + // Expect(err).Should(BeNil()) + // err = rpcClient.CallContext(ctx, nil, "eth_sendRawTransaction", teleporterDeployerTransaction) + // Expect(err).Should(BeNil()) + // time.Sleep(5 * time.Second) + // teleporterCode, err := chainBRPCClient.CodeAt(ctx, teleporterContractAddress, nil) + // Expect(err).Should(BeNil()) + // Expect(len(teleporterCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode + // } + // log.Info("Finished deploying Teleporter contracts") log.Info("Set up ginkgo before suite") }) @@ -295,15 +297,315 @@ var _ = ginkgo.AfterSuite(func() { // will then send a transaction to the source subnet to issue a Warp message simulting a transaction // sent from the Teleporter contract. The relayer will then wait for the transaction to be confirmed // on the destination subnet and verify that the Warp message was received and unpacked correctly. -var _ = ginkgo.Describe("[Relayer E2E]", ginkgo.Ordered, func() { +// var _ = ginkgo.Describe("[Relayer E2E]", ginkgo.Ordered, func() { +// var ( +// receivedWarpMessage *avalancheWarp.Message +// payload []byte +// relayerCmd *exec.Cmd +// relayerCancel context.CancelFunc +// ) + +// ginkgo.It("Set up relayer config", ginkgo.Label("Relayer", "Setup Relayer"), func() { +// hostA, portA, err := getURIHostAndPort(chainANodeURIs[0]) +// Expect(err).Should(BeNil()) + +// hostB, portB, err := getURIHostAndPort(chainBNodeURIs[0]) +// Expect(err).Should(BeNil()) + +// log.Info( +// "Setting up relayer config", +// "hostA", hostA, +// "portA", portA, +// "blockChainA", blockchainIDA.String(), +// "hostB", hostB, +// "portB", portB, +// "blockChainB", blockchainIDB.String(), +// "subnetA", subnetA.String(), +// "subnetB", subnetB.String(), +// ) + +// relayerConfig := config.Config{ +// LogLevel: logging.Info.LowerString(), +// NetworkID: peers.LocalNetworkID, +// PChainAPIURL: chainANodeURIs[0], +// EncryptConnection: false, +// StorageLocation: storageLocation, +// SourceSubnets: []config.SourceSubnet{ +// { +// SubnetID: subnetA.String(), +// ChainID: blockchainIDA.String(), +// VM: config.EVM.String(), +// EncryptConnection: false, +// APINodeHost: hostA, +// APINodePort: portA, +// MessageContracts: map[string]config.MessageProtocolConfig{ +// teleporterContractAddress.Hex(): { +// MessageFormat: config.TELEPORTER.String(), +// Settings: map[string]interface{}{ +// "reward-address": fundedAddress.Hex(), +// }, +// }, +// }, +// }, +// }, +// DestinationSubnets: []config.DestinationSubnet{ +// { +// SubnetID: subnetB.String(), +// ChainID: blockchainIDB.String(), +// VM: config.EVM.String(), +// EncryptConnection: false, +// APINodeHost: hostB, +// APINodePort: portB, +// AccountPrivateKey: fundedKeyStr, +// }, +// }, +// } + +// data, err := json.MarshalIndent(relayerConfig, "", "\t") +// Expect(err).Should(BeNil()) + +// f, err := os.CreateTemp(os.TempDir(), "relayer-config.json") +// Expect(err).Should(BeNil()) + +// _, err = f.Write(data) +// Expect(err).Should(BeNil()) +// relayerConfigPath = f.Name() + +// log.Info("Created awm-relayer config", "configPath", relayerConfigPath, "config", string(data)) +// }) + +// ginkgo.It("Build Relayer", ginkgo.Label("Relayer", "Build Relayer"), func() { +// // Build the awm-relayer binary +// cmd := exec.Command("./scripts/build.sh") +// out, err := cmd.CombinedOutput() +// fmt.Println(string(out)) +// Expect(err).Should(BeNil()) +// }) + +// // Send a transaction to Subnet A to issue a Warp Message from the Teleporter contract to Subnet B +// ginkgo.It("Send Message from A to B", ginkgo.Label("Warp", "SendWarp"), func() { +// ctx := context.Background() + +// relayerCmd, relayerCancel = runRelayerExecutable(ctx) + +// nonceA, err := chainARPCClient.NonceAt(ctx, fundedAddress, nil) +// Expect(err).Should(BeNil()) + +// nonceB, err := chainBRPCClient.NonceAt(ctx, fundedAddress, nil) +// Expect(err).Should(BeNil()) + +// log.Info("Packing teleporter message", "nonceA", nonceA, "nonceB", nonceB) +// payload, err = teleporter.PackSendCrossChainMessageEvent(common.Hash(blockchainIDB), teleporterMessage) +// Expect(err).Should(BeNil()) + +// data, err := teleporter.EVMTeleporterContractABI.Pack( +// "sendCrossChainMessage", +// TeleporterMessageInput{ +// DestinationChainID: blockchainIDB, +// DestinationAddress: fundedAddress, +// FeeInfo: FeeInfo{ +// ContractAddress: fundedAddress, +// Amount: big.NewInt(0), +// }, +// RequiredGasLimit: big.NewInt(1), +// AllowedRelayerAddresses: []common.Address{}, +// Message: []byte{1, 2, 3, 4}, +// }, +// ) +// Expect(err).Should(BeNil()) + +// // Send a transaction to the Teleporter contract +// tx := newTestTeleporterMessage(chainAIDInt, teleporterContractAddress, nonceA, data) + +// txSigner := types.LatestSignerForChainID(chainAIDInt) +// signedTx, err := types.SignTx(tx, txSigner, fundedKey) +// Expect(err).Should(BeNil()) + +// // Sleep for some time to make sure relayer has started up and subscribed. +// time.Sleep(15 * time.Second) +// log.Info("Subscribing to new heads on destination chain") + +// newHeadsB := make(chan *types.Header, 10) +// sub, err := chainBWSClient.SubscribeNewHead(ctx, newHeadsB) +// Expect(err).Should(BeNil()) +// defer sub.Unsubscribe() + +// log.Info("Sending sendWarpMessage transaction", "destinationChainID", blockchainIDB, "txHash", signedTx.Hash()) +// err = chainARPCClient.SendTransaction(ctx, signedTx) +// Expect(err).Should(BeNil()) + +// time.Sleep(5 * time.Second) +// receipt, err := chainARPCClient.TransactionReceipt(ctx, signedTx.Hash()) +// Expect(err).Should(BeNil()) +// Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + +// // Get the latest block from Subnet B +// log.Info("Waiting for new block confirmation") +// newHead := <-newHeadsB +// log.Info("Received new head", "height", newHead.Number.Uint64()) +// blockHash := newHead.Hash() +// block, err := chainBRPCClient.BlockByHash(ctx, blockHash) +// Expect(err).Should(BeNil()) +// log.Info( +// "Got block", +// "blockHash", blockHash, +// "blockNumber", block.NumberU64(), +// "transactions", block.Transactions(), +// "numTransactions", len(block.Transactions()), +// "block", block, +// ) +// accessLists := block.Transactions()[0].AccessList() +// Expect(len(accessLists)).Should(Equal(1)) +// Expect(accessLists[0].Address).Should(Equal(warp.Module.Address)) + +// // Check the transaction storage key has warp message we're expecting +// storageKeyHashes := accessLists[0].StorageKeys +// packedPredicate := predicateutils.HashSliceToBytes(storageKeyHashes) +// predicateBytes, err := predicateutils.UnpackPredicate(packedPredicate) +// Expect(err).Should(BeNil()) +// receivedWarpMessage, err = avalancheWarp.ParseMessage(predicateBytes) +// Expect(err).Should(BeNil()) + +// // Check that the transaction has successful receipt status +// txHash := block.Transactions()[0].Hash() +// receipt, err = chainBRPCClient.TransactionReceipt(ctx, txHash) +// Expect(err).Should(BeNil()) +// Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + +// log.Info("Finished sending warp message, closing down output channel") + +// // Cancel the command and stop the relayer +// relayerCancel() +// _ = relayerCmd.Wait() +// }) + +// ginkgo.It("Try relaying already delivered message", ginkgo.Label("Relayer", "RelayerAlreadyDeliveredMessage"), func() { +// ctx := context.Background() +// logger := logging.NewLogger( +// "awm-relayer", +// logging.NewWrappedCore( +// logging.Info, +// os.Stdout, +// logging.JSON.ConsoleEncoder(), +// ), +// ) +// jsonDB, err := database.NewJSONFileStorage(logger, storageLocation, []ids.ID{blockchainIDA, blockchainIDB}) +// Expect(err).Should(BeNil()) + +// // Modify the JSON database to force the relayer to re-process old blocks +// jsonDB.Put(blockchainIDA, []byte(database.LatestProcessedBlockKey), []byte("0")) +// jsonDB.Put(blockchainIDB, []byte(database.LatestProcessedBlockKey), []byte("0")) + +// // Subscribe to the destination chain block published +// newHeadsB := make(chan *types.Header, 10) +// sub, err := chainBWSClient.SubscribeNewHead(ctx, newHeadsB) +// Expect(err).Should(BeNil()) +// defer sub.Unsubscribe() + +// // Run the relayer +// relayerCmd, relayerCancel = runRelayerExecutable(ctx) + +// // We should not receive a new block on subnet B, since the relayer should have seen the Teleporter message was already delivered +// Consistently(newHeadsB, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive()) + +// // Cancel the command and stop the relayer +// relayerCancel() +// _ = relayerCmd.Wait() +// }) + +// ginkgo.It("Validate Received Warp Message Values", ginkgo.Label("Relayer", "VerifyWarp"), func() { +// Expect(receivedWarpMessage.SourceChainID).Should(Equal(blockchainIDA)) +// addressedPayload, err := warpPayload.ParseAddressedPayload(receivedWarpMessage.Payload) +// Expect(err).Should(BeNil()) + +// receivedDestinationID, err := ids.ToID(addressedPayload.DestinationChainID.Bytes()) +// Expect(err).Should(BeNil()) +// Expect(receivedDestinationID).Should(Equal(blockchainIDB)) +// Expect(addressedPayload.DestinationAddress).Should(Equal(teleporterContractAddress)) +// Expect(addressedPayload.Payload).Should(Equal(payload)) + +// // Check that the teleporter message is correct +// receivedTeleporterMessage, err := teleporter.UnpackTeleporterMessage(addressedPayload.Payload) +// Expect(err).Should(BeNil()) +// Expect(*receivedTeleporterMessage).Should(Equal(teleporterMessage)) +// }) +// }) + +var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { var ( - receivedWarpMessage *avalancheWarp.Message - payload []byte - relayerCmd *exec.Cmd - relayerCancel context.CancelFunc + relayerCmd *exec.Cmd + relayerCancel context.CancelFunc + blockHashReceiverAddressB common.Address + subnetAHashes []common.Hash + blockHashABI *abi.ABI ) + ginkgo.It("Deploy block hash receiver", ginkgo.Label("Relayer", "DeployBlockHashReceiver"), func() { + ctx := context.Background() + blockHashReceiverByteCode := readHexTextFile("./tests/BlockHashReceiverByteCode.txt") + + nonceB, err := chainBRPCClient.NonceAt(ctx, fundedAddress, nil) + Expect(err).Should(BeNil()) + + // gasTipCapA, err := chainARPCClient.SuggestGasTipCap(context.Background()) + // Expect(err).Should(BeNil()) + + // baseFeeA, err := chainARPCClient.EstimateBaseFee(context.Background()) + // Expect(err).Should(BeNil()) + // gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) + // gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) + + // contractAuth, err := bind.NewKeyedTransactorWithChainID(fundedKey, big.NewInt(peers.LocalNetworkID)) + // contractAuth.Nonce = big.NewInt(int64(nonceA)) + // contractAuth.GasFeeCap = gasFeeCapA + // contractAuth.GasTipCap = gasTipCapA + // contractAuth.GasLimit = 1000000 + + // Expect(err).Should(BeNil()) + // blockHashReceiverAddressB, _, _, err = bind.DeployContract( + // contractAuth, + // *blockHashABI, + // common.FromHex(blockHashReceiverByteCode), + // chainBRPCClient, + // ) + // Expect(err).Should(BeNil()) + blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() + Expect(err).Should(BeNil()) + blockHashReceiverAddressB, err = deriveEVMContractAddress(fundedAddress, nonceB) + Expect(err).Should(BeNil()) + + cmdOutput := make(chan string) + cmd := exec.Command( + "cast", + "send", + "--rpc-url", chainBRPCURI, + "--private-key", hexutil.Encode(fundedKey.D.Bytes()), + "--create", blockHashReceiverByteCode, + ) + + // Set up a pipe to capture the command's output + cmdReader, _ := cmd.StdoutPipe() + + // Start a goroutine to read and output the command's stdout + go func() { + scanner := bufio.NewScanner(cmdReader) + for scanner.Scan() { + log.Info(scanner.Text()) + } + cmdOutput <- "Command execution finished" + }() + + err = cmd.Run() + Expect(err).Should(BeNil()) + + time.Sleep(5 * time.Second) + deployedCode, err := chainBRPCClient.CodeAt(ctx, blockHashReceiverAddressB, nil) + Expect(err).Should(BeNil()) + Expect(len(deployedCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode + + log.Info("Deployed block hash receiver contract", "address", blockHashReceiverAddressB.Hex()) + }) - ginkgo.It("Set up relayer config", ginkgo.Label("Relayer", "Setup Relayer"), func() { + ginkgo.It("Set up relayer config", ginkgo.Label("Relayer", "SetupRelayer"), func() { hostA, portA, err := getURIHostAndPort(chainANodeURIs[0]) Expect(err).Should(BeNil()) @@ -332,15 +634,25 @@ var _ = ginkgo.Describe("[Relayer E2E]", ginkgo.Ordered, func() { { SubnetID: subnetA.String(), ChainID: blockchainIDA.String(), - VM: config.EVM.String(), + VM: config.EVM_BLOCKHASH.String(), EncryptConnection: false, APINodeHost: hostA, APINodePort: portA, MessageContracts: map[string]config.MessageProtocolConfig{ - teleporterContractAddress.Hex(): { - MessageFormat: config.TELEPORTER.String(), + "0x0000000000000000000000000000000000000000": { + MessageFormat: config.BLOCK_HASH_PUBLISHER.String(), Settings: map[string]interface{}{ - "reward-address": fundedAddress.Hex(), + "destination-chains": []struct { + ChainID string `json:"chain-id"` + Address string `json:"address"` + Interval string `json:"interval"` + }{ + { + ChainID: blockchainIDB.String(), + Address: blockHashReceiverAddressB.String(), + Interval: "5", + }, + }, }, }, }, @@ -372,7 +684,7 @@ var _ = ginkgo.Describe("[Relayer E2E]", ginkgo.Ordered, func() { log.Info("Created awm-relayer config", "configPath", relayerConfigPath, "config", string(data)) }) - ginkgo.It("Build Relayer", ginkgo.Label("Relayer", "Build Relayer"), func() { + ginkgo.It("Build Relayer", ginkgo.Label("Relayer", "BuildRelayer"), func() { // Build the awm-relayer binary cmd := exec.Command("./scripts/build.sh") out, err := cmd.CombinedOutput() @@ -381,150 +693,146 @@ var _ = ginkgo.Describe("[Relayer E2E]", ginkgo.Ordered, func() { }) // Send a transaction to Subnet A to issue a Warp Message from the Teleporter contract to Subnet B - ginkgo.It("Send Message from A to B", ginkgo.Label("Warp", "SendWarp"), func() { + ginkgo.It("Publish block hash", ginkgo.Label("Relayer", "PublishHash"), func() { ctx := context.Background() relayerCmd, relayerCancel = runRelayerExecutable(ctx) - nonceA, err := chainARPCClient.NonceAt(ctx, fundedAddress, nil) Expect(err).Should(BeNil()) - nonceB, err := chainBRPCClient.NonceAt(ctx, fundedAddress, nil) - Expect(err).Should(BeNil()) - - log.Info("Packing teleporter message", "nonceA", nonceA, "nonceB", nonceB) - payload, err = teleporter.PackSendCrossChainMessageEvent(common.Hash(blockchainIDB), teleporterMessage) - Expect(err).Should(BeNil()) - - data, err := teleporter.EVMTeleporterContractABI.Pack( - "sendCrossChainMessage", - TeleporterMessageInput{ - DestinationChainID: blockchainIDB, - DestinationAddress: fundedAddress, - FeeInfo: FeeInfo{ - ContractAddress: fundedAddress, - Amount: big.NewInt(0), - }, - RequiredGasLimit: big.NewInt(1), - AllowedRelayerAddresses: []common.Address{}, - Message: []byte{1, 2, 3, 4}, - }, - ) - Expect(err).Should(BeNil()) - - // Send a transaction to the Teleporter contract - tx := newTestTeleporterMessage(chainAIDInt, teleporterContractAddress, nonceA, data) - - txSigner := types.LatestSignerForChainID(chainAIDInt) - signedTx, err := types.SignTx(tx, txSigner, fundedKey) - Expect(err).Should(BeNil()) - - // Sleep for some time to make sure relayer has started up and subscribed. - time.Sleep(15 * time.Second) - log.Info("Subscribing to new heads on destination chain") - - newHeadsB := make(chan *types.Header, 10) - sub, err := chainBWSClient.SubscribeNewHead(ctx, newHeadsB) - Expect(err).Should(BeNil()) - defer sub.Unsubscribe() - - log.Info("Sending sendWarpMessage transaction", "destinationChainID", blockchainIDB, "txHash", signedTx.Hash()) - err = chainARPCClient.SendTransaction(ctx, signedTx) - Expect(err).Should(BeNil()) - - time.Sleep(5 * time.Second) - receipt, err := chainARPCClient.TransactionReceipt(ctx, signedTx.Hash()) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - - // Get the latest block from Subnet B - log.Info("Waiting for new block confirmation") - newHead := <-newHeadsB - log.Info("Received new head", "height", newHead.Number.Uint64()) - blockHash := newHead.Hash() - block, err := chainBRPCClient.BlockByHash(ctx, blockHash) - Expect(err).Should(BeNil()) - log.Info( - "Got block", - "blockHash", blockHash, - "blockNumber", block.NumberU64(), - "transactions", block.Transactions(), - "numTransactions", len(block.Transactions()), - "block", block, - ) - accessLists := block.Transactions()[0].AccessList() - Expect(len(accessLists)).Should(Equal(1)) - Expect(accessLists[0].Address).Should(Equal(warp.Module.Address)) - - // Check the transaction storage key has warp message we're expecting - storageKeyHashes := accessLists[0].StorageKeys - packedPredicate := predicateutils.HashSliceToBytes(storageKeyHashes) - predicateBytes, err := predicateutils.UnpackPredicate(packedPredicate) - Expect(err).Should(BeNil()) - receivedWarpMessage, err = avalancheWarp.ParseMessage(predicateBytes) + destinationAddress := common.HexToAddress("0x0000000000000000000000000000000000000000") + gasTipCapA, err := chainARPCClient.SuggestGasTipCap(context.Background()) Expect(err).Should(BeNil()) - // Check that the transaction has successful receipt status - txHash := block.Transactions()[0].Hash() - receipt, err = chainBRPCClient.TransactionReceipt(ctx, txHash) + baseFeeA, err := chainARPCClient.EstimateBaseFee(context.Background()) Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - - log.Info("Finished sending warp message, closing down output channel") - - // Cancel the command and stop the relayer - relayerCancel() - _ = relayerCmd.Wait() - }) + gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) + gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) - ginkgo.It("Try relaying already delivered message", ginkgo.Label("Relayer", "RelayerAlreadyDeliveredMessage"), func() { - ctx := context.Background() - logger := logging.NewLogger( - "awm-relayer", - logging.NewWrappedCore( - logging.Info, - os.Stdout, - logging.JSON.ConsoleEncoder(), - ), - ) - jsonDB, err := database.NewJSONFileStorage(logger, storageLocation, []ids.ID{blockchainIDA, blockchainIDB}) + // Subscribe to the destination chain block published + newHeadsA := make(chan *types.Header, 10) + subA, err := chainAWSClient.SubscribeNewHead(ctx, newHeadsA) Expect(err).Should(BeNil()) - - // Modify the JSON database to force the relayer to re-process old blocks - jsonDB.Put(blockchainIDA, []byte(database.LatestProcessedBlockKey), []byte("0")) - jsonDB.Put(blockchainIDB, []byte(database.LatestProcessedBlockKey), []byte("0")) + defer subA.Unsubscribe() // Subscribe to the destination chain block published newHeadsB := make(chan *types.Header, 10) - sub, err := chainBWSClient.SubscribeNewHead(ctx, newHeadsB) - Expect(err).Should(BeNil()) - defer sub.Unsubscribe() + subB, err := chainBWSClient.SubscribeNewHead(ctx, newHeadsB) + Expect(err).Should(BeNil()) + defer subB.Unsubscribe() + + // TODONOW: not necessarily true, since the block height might be < 5 + // Send 6 transactions to produce 6 blocks on subnet A + // We expect the first and the sixth txs to be published by the relayer, since the relayer + // will be initialized with the "latest published block" to be height 0 + + for i := 0; i < 6; i++ { + value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1)) // 1eth + txA := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainAIDInt, + Nonce: nonceA + uint64(i), + To: &destinationAddress, + Gas: defaultTeleporterMessageGas, + GasFeeCap: gasFeeCapA, + GasTipCap: gasTipCapA, + Value: value, + }) + txSignerA := types.LatestSignerForChainID(chainAIDInt) + triggerTxA, err := types.SignTx(txA, txSignerA, fundedKey) + Expect(err).Should(BeNil()) + err = chainARPCClient.SendTransaction(ctx, triggerTxA) + Expect(err).Should(BeNil()) + + log.Info("Waiting for new block confirmation", "block", i) + newHeadA := <-newHeadsA + subnetAHashes = append(subnetAHashes, newHeadA.Hash()) + } - // Run the relayer - relayerCmd, relayerCancel = runRelayerExecutable(ctx) + time.Sleep(5 * time.Second) - // We should not receive a new block on subnet B, since the relayer should have seen the Teleporter message was already delivered - Consistently(newHeadsB, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive()) + for { + newHeadB := <-newHeadsB + log.Info("Fetching log from the newly produced block") + + blockHashB := newHeadB.Hash() + + block, err := chainBRPCClient.BlockByHash(ctx, blockHashB) + Expect(err).Should(BeNil()) + txs := block.Transactions() + log.Info(fmt.Sprintf("numTxs: %d", len(txs))) + for _, tx := range txs { + log.Info(fmt.Sprintf("txHash: %s", tx.Hash().String())) + log.Info(fmt.Sprintf("to: %s", tx.To().String())) + log.Info(fmt.Sprintf("data: %s", hex.EncodeToString(tx.Data()))) + receipt, err := chainBRPCClient.TransactionReceipt(ctx, tx.Hash()) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + } + + // receipt, err := chainBRPCClient.TransactionReceipt(ctx, blockHashB) + // Expect(err).Should(BeNil()) + // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + log.Info("event", blockHashABI.Events["ReceiveBlockHash"].ID.String()) //TODONOW remove this + logs, err := chainBRPCClient.FilterLogs(ctx, interfaces.FilterQuery{ + // BlockHash: &blockHashB, + Addresses: []common.Address{blockHashReceiverAddressB}, + // Topics: [][]common.Hash{ + // { + // blockHashABI.Events["ReceiveBlockHash"].ID, + // }, + // }, + }) + Expect(err).Should(BeNil()) + log.Info("Logs", "logs", logs) + } + // // Wait for new blocks on B. We are expecting 2 new blocks + // { + // newHeadB := <-newHeadsB + // blockHashB := newHeadB.Hash() + + // log.Info("Fetching relevant warp logs from the newly produced block") + // logs, err := chainBRPCClient.FilterLogs(ctx, interfaces.FilterQuery{ + // BlockHash: &blockHashB, + // // Addresses: []common.Address{blockHashReceiverAddressB}, + // // Topics: [][]common.Hash{ + // // { + // // blockHashABI.Events["ReceiveBlockHash"].ID, + // // }, + // // }, + // }) + // log.Info("Logs", "logs", logs) + // Expect(err).Should(BeNil()) + // Expect(len(logs)).Should(Equal(1)) + + // Expect(logs[0].Topics[2]).Should(Equal(subnetAHashes[0])) + // } + // { + // newHeadB := <-newHeadsB + // blockHashB := newHeadB.Hash() + + // log.Info("Fetching relevant warp logs from the newly produced block") + // logs, err := chainBRPCClient.FilterLogs(ctx, interfaces.FilterQuery{ + // BlockHash: &blockHashB, + // Addresses: []common.Address{blockHashReceiverAddressB}, + // Topics: [][]common.Hash{ + // { + // blockHashABI.Events["ReceiveBlockHash"].ID, + // }, + // }, + // }) + // Expect(err).Should(BeNil()) + // Expect(len(logs)).Should(Equal(1)) + + // Expect(logs[0].Topics[2]).Should(Equal(subnetAHashes[5])) + // } // Cancel the command and stop the relayer relayerCancel() _ = relayerCmd.Wait() }) - ginkgo.It("Validate Received Warp Message Values", ginkgo.Label("Relayer", "VerifyWarp"), func() { - Expect(receivedWarpMessage.SourceChainID).Should(Equal(blockchainIDA)) - addressedPayload, err := warpPayload.ParseAddressedPayload(receivedWarpMessage.Payload) - Expect(err).Should(BeNil()) + ginkgo.It("Verify received block hash", ginkgo.Label("Relayer", "VerifyReceivedHash"), func() { - receivedDestinationID, err := ids.ToID(addressedPayload.DestinationChainID.Bytes()) - Expect(err).Should(BeNil()) - Expect(receivedDestinationID).Should(Equal(blockchainIDB)) - Expect(addressedPayload.DestinationAddress).Should(Equal(teleporterContractAddress)) - Expect(addressedPayload.Payload).Should(Equal(payload)) - - // Check that the teleporter message is correct - receivedTeleporterMessage, err := teleporter.UnpackTeleporterMessage(addressedPayload.Payload) - Expect(err).Should(BeNil()) - Expect(*receivedTeleporterMessage).Should(Equal(teleporterMessage)) }) }) diff --git a/tests/utils.go b/tests/utils.go index cd60ab3b..d1241c6c 100644 --- a/tests/utils.go +++ b/tests/utils.go @@ -21,8 +21,11 @@ import ( "github.com/ava-labs/subnet-evm/tests/utils" "github.com/ava-labs/subnet-evm/tests/utils/runner" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" . "github.com/onsi/gomega" + "github.com/pkg/errors" ) var ( @@ -140,3 +143,18 @@ func setUpProposerVm(ctx context.Context, fundedKey *ecdsa.PrivateKey, manager * err = utils.IssueTxsToActivateProposerVMFork(ctx, chainIDInt, fundedKey, client) Expect(err).Should(BeNil()) } + +// TODONOW: remove this once available in teleporter +func deriveEVMContractAddress(sender common.Address, nonce uint64) (common.Address, error) { + type AddressNonce struct { + Address common.Address + Nonce uint64 + } + addressNonce := AddressNonce{sender, nonce} + rlpEncoded, err := rlp.EncodeToBytes(addressNonce) + if err != nil { + return common.Address{}, errors.Wrap(err, "Failed to RLP encode address and nonce value.") + } + hash := crypto.Keccak256Hash(rlpEncoded) + return common.HexToAddress(fmt.Sprintf("0x%x", hash.Bytes()[12:])), nil +} diff --git a/vms/evm_block_hash/subscriber.go b/vms/evm_block_hash/subscriber.go index 96dff49e..328999b7 100644 --- a/vms/evm_block_hash/subscriber.go +++ b/vms/evm_block_hash/subscriber.go @@ -2,6 +2,7 @@ package evm_block_hash import ( "context" + "encoding/json" "errors" "fmt" "math/big" @@ -34,13 +35,14 @@ var ( // subscriber implements Subscriber type subscriber struct { - nodeWSURL string - nodeRPCURL string - chainID ids.ID - logsChan chan vmtypes.WarpMessageInfo - blocks <-chan *types.Header - sub interfaces.Subscription - networkID uint32 + nodeWSURL string + nodeRPCURL string + chainID ids.ID + logsChan chan vmtypes.WarpMessageInfo + blocks <-chan *types.Header + sub interfaces.Subscription + networkID uint32 + destinationChainIDs []ids.ID logger logging.Logger db database.RelayerDatabase @@ -59,14 +61,54 @@ func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db dat logs := make(chan vmtypes.WarpMessageInfo, maxClientSubscriptionBuffer) + var destinationChainIDs []ids.ID + // Extract the destination chain IDs from the EVM Block Hash config + for _, cfg := range subnetInfo.MessageContracts { + if config.ParseMessageProtocol(cfg.MessageFormat) == config.BLOCK_HASH_PUBLISHER { + // Marshal the map and unmarshal into the Block Hash Publisher config + data, err := json.Marshal(cfg.Settings) + if err != nil { + logger.Error("Failed to marshal Block Hash Publisher config") + return nil + } + // TODONOW: Don't repeat the block hash config here. Need to resolve dependency cycle + type destinationInfo struct { + ChainID string `json:"chain-id"` + Address string `json:"address"` + Interval string `json:"interval"` + } + + type config struct { + DestinationChains []destinationInfo `json:"destination-chains"` + } + var messageConfig config + if err := json.Unmarshal(data, &messageConfig); err != nil { + logger.Error("Failed to unmarshal Block Hash Publisher config") + return nil + } + for _, chainInfo := range messageConfig.DestinationChains { + chainID, err := ids.FromString(chainInfo.ChainID) + if err != nil { + logger.Error( + "Failed to decode base-58 encoded destination chain ID", + zap.Error(err), + ) + return nil + } + destinationChainIDs = append(destinationChainIDs, chainID) + } + } + } + return &subscriber{ - nodeWSURL: subnetInfo.GetNodeWSEndpoint(), - nodeRPCURL: subnetInfo.GetNodeRPCEndpoint(), - chainID: chainID, - logger: logger, - db: db, - logsChan: logs, - networkID: config.GetNetworkID(), + nodeWSURL: subnetInfo.GetNodeWSEndpoint(), + nodeRPCURL: subnetInfo.GetNodeRPCEndpoint(), + chainID: chainID, + logger: logger, + db: db, + logsChan: logs, + networkID: config.GetNetworkID(), + destinationChainIDs: destinationChainIDs, } } @@ -128,7 +170,7 @@ func (s *subscriber) dialAndSubscribe() error { return nil } -func (s *subscriber) NewWarpMessageInfo(block *types.Header) (*vmtypes.WarpMessageInfo, error) { +func (s *subscriber) NewWarpMessageInfo(block *types.Header, destinationChainID ids.ID) (*vmtypes.WarpMessageInfo, error) { blockHashPayload, err := payload.NewBlockHashPayload(block.Hash()) if err != nil { return nil, err @@ -143,24 +185,30 @@ func (s *subscriber) NewWarpMessageInfo(block *types.Header) (*vmtypes.WarpMessa } return &vmtypes.WarpMessageInfo{ - UnsignedMsgBytes: unsignedMessage.Bytes(), - BlockNumber: block.Number.Uint64(), - BlockTimestamp: block.Time, + DestinationChainID: destinationChainID, + UnsignedMsgBytes: unsignedMessage.Bytes(), + BlockNumber: block.Number.Uint64(), + BlockTimestamp: block.Time, }, nil } // forward logs from the concrete log channel to the interface channel func (s *subscriber) forwardLogs() { + s.logger.Info("DEBUG Forwarding logs") for block := range s.blocks { - messageInfo, err := s.NewWarpMessageInfo(block) - if err != nil { - s.logger.Error( - "Invalid log. Continuing.", - zap.Error(err), - ) - continue + // Fan out to all destination chains + for _, destinationChainID := range s.destinationChainIDs { + s.logger.Info("DEBUG forwarding to destination", zap.String("destinationChainID", destinationChainID.String())) + messageInfo, err := s.NewWarpMessageInfo(block, destinationChainID) + if err != nil { + s.logger.Error( + "Invalid log. Continuing.", + zap.Error(err), + ) + continue + } + s.logsChan <- *messageInfo } - s.logsChan <- *messageInfo } } From 40c568a3fc28f25943c9e55fd4a3a580be8860c5 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Tue, 3 Oct 2023 22:33:21 +0000 Subject: [PATCH 08/26] fan out in message manager --- .../block_hash_publisher/message_manager.go | 146 ++++++++---------- messages/message_manager.go | 2 +- messages/teleporter/message_manager.go | 5 +- relayer/message_relayer.go | 21 +-- relayer/message_relayer_metrics.go | 6 +- relayer/relayer.go | 2 +- tests/e2e_test.go | 9 +- vms/evm_block_hash/subscriber.go | 105 ++++--------- 8 files changed, 115 insertions(+), 181 deletions(-) diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index a77199ff..6e61acef 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -34,6 +34,21 @@ type destinationSenderInfo struct { lastBlock uint64 } +func (d *destinationSenderInfo) shouldSend(blockTimestamp uint64, blockNumber uint64) bool { + if d.useTimeInterval { + interval := d.timeIntervalSeconds + if time.Unix(int64(blockTimestamp), 0).Sub(time.Unix(int64(d.lastTimeSent), 0)) < (time.Duration(interval) * time.Second) { + return false + } + } else { + interval := d.blockInterval + if blockNumber-d.lastBlock < uint64(interval) { + return false + } + } + return true +} + type messageManager struct { destinations map[ids.ID]*destinationSenderInfo logger logging.Logger @@ -83,8 +98,6 @@ func NewMessageManager( address: common.HexToAddress(destination.Address), client: destinationClients[destinationID], } - logger.Info("DEBUG DESTINATIONS", zap.String("address", destinations[destinationID].address.String())) - } return &messageManager{ @@ -93,96 +106,71 @@ func NewMessageManager( }, nil } -// ShouldSendMessage returns true if the message should be sent to the destination chain -func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) { - destination, ok := m.destinations[destinationChainID] - if !ok { - var destinationIDs []string - for id := range m.destinations { - destinationIDs = append(destinationIDs, id.String()) - } - m.logger.Info( - "DEBUG", - zap.String("destinationChainID", destinationChainID.String()), - zap.String("configuredDestinations", fmt.Sprintf("%#v", destinationIDs)), - ) - return false, fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID) - } - if destination.useTimeInterval { - interval := destination.timeIntervalSeconds - if time.Unix(int64(warpMessageInfo.BlockTimestamp), 0).Sub(time.Unix(int64(destination.lastTimeSent), 0)) < (time.Duration(interval) * time.Second) { - return false, nil - } - } else { - interval := destination.blockInterval - if warpMessageInfo.BlockNumber-destination.lastBlock < uint64(interval) { - m.logger.Info( - "DEBUG", - zap.String("decision", "Not sending"), - zap.Int("blockNum", int(warpMessageInfo.BlockNumber)), - zap.Int("lastBlockNum", int(destination.lastBlock)), - ) - return false, nil +// ShouldSendMessage returns true if the message should be sent to ANY of the configured destination chains +// This saves us from having to aggregate signatures in that case. Decisions about which destination chains to send to are made in SendMessage +func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, _ ids.ID) (bool, error) { + for _, destination := range m.destinations { + if destination.shouldSend(warpMessageInfo.BlockTimestamp, warpMessageInfo.BlockNumber) { + return true, nil } } - // Set the last approved block/time here. We don't set the last sent block/time until the message is actually sent - destination.lastApprovedBlock = warpMessageInfo.BlockNumber - destination.lastApprovedTime = warpMessageInfo.BlockTimestamp - m.logger.Info( - "DEBUG", - zap.String("decision", "Sending"), - zap.Int("blockNum", int(warpMessageInfo.BlockNumber)), - zap.Int("lastBlockNum", int(destination.lastBlock)), - ) - return true, nil + return false, nil } -func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { +func (m *messageManager) SendMessage(signedMessage *warp.Message, warpMessageInfo *vmtypes.WarpMessageInfo, _ ids.ID) error { m.logger.Info( - "Sending message to destination chain", - zap.String("destinationChainID", destinationChainID.String()), - zap.String("warpMessageID", signedMessage.ID().String()), + "DEBUG SENDING", + zap.String("destinationInfo", fmt.Sprintf("%#v", m.destinations)), + zap.Uint64("blockTimestampe", warpMessageInfo.BlockTimestamp), + zap.Uint64("blockNumber", warpMessageInfo.BlockNumber), ) - // Construct the transaction call data to call the receive cross chain message method of the receiver precompile. - callData, err := teleporter_block_hash.PackReceiveBlockHash(teleporter_block_hash.ReceiveBlockHashInput{ - MessageIndex: uint32(0), - SourceChainID: signedMessage.SourceChainID, - }) - if err != nil { - m.logger.Error( - "Failed packing receiveCrossChainMessage call data", - zap.Error(err), - zap.Uint32("messageIndex", 0), - zap.String("sourceChainID", signedMessage.SourceChainID.String()), + for destinationChainID, destination := range m.destinations { + if !destination.shouldSend(warpMessageInfo.BlockTimestamp, warpMessageInfo.BlockNumber) { + continue + } + + m.logger.Info( + "Sending message to destination chain", zap.String("destinationChainID", destinationChainID.String()), zap.String("warpMessageID", signedMessage.ID().String()), ) - return err - } + // Construct the transaction call data to call the receive cross chain message method of the receiver precompile. + callData, err := teleporter_block_hash.PackReceiveBlockHash(teleporter_block_hash.ReceiveBlockHashInput{ + MessageIndex: uint32(0), + SourceChainID: signedMessage.SourceChainID, + }) + if err != nil { + m.logger.Error( + "Failed packing receiveCrossChainMessage call data", + zap.Error(err), + zap.Uint32("messageIndex", 0), + zap.String("sourceChainID", signedMessage.SourceChainID.String()), + zap.String("destinationChainID", destinationChainID.String()), + zap.String("warpMessageID", signedMessage.ID().String()), + ) + return err + } - // Get the correct destination client from the global map - destination, ok := m.destinations[destinationChainID] - if !ok { - return fmt.Errorf("relayer not configured to deliver to destination. destinationChainID=%s", destinationChainID) - } - err = destination.client.SendTx(signedMessage, destination.address.Hex(), publishBlockHashGasLimit, callData) - if err != nil { - m.logger.Error( - "Failed to send tx.", + // Get the correct destination client from the global map + err = destination.client.SendTx(signedMessage, destination.address.Hex(), publishBlockHashGasLimit, callData) + if err != nil { + m.logger.Error( + "Failed to send tx.", + zap.String("destinationChainID", destinationChainID.String()), + zap.String("warpMessageID", signedMessage.ID().String()), + zap.Error(err), + ) + return err + } + + // Set the last sent block/time + destination.lastTimeSent = destination.lastApprovedTime + destination.lastBlock = destination.lastApprovedBlock + m.logger.Info( + "Sent message to destination chain", zap.String("destinationChainID", destinationChainID.String()), zap.String("warpMessageID", signedMessage.ID().String()), - zap.Error(err), ) - return err } - - // Set the last sent block/time - destination.lastTimeSent = destination.lastApprovedTime - destination.lastBlock = destination.lastApprovedBlock - m.logger.Info( - "Sent message to destination chain", - zap.String("destinationChainID", destinationChainID.String()), - zap.String("warpMessageID", signedMessage.ID().String()), - ) return nil } diff --git a/messages/message_manager.go b/messages/message_manager.go index 8a649430..93b201a8 100644 --- a/messages/message_manager.go +++ b/messages/message_manager.go @@ -27,7 +27,7 @@ type MessageManager interface { ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) (bool, error) // SendMessage sends the signed message to the destination chain. The payload parsed according to // the VM rules is also passed in, since MessageManager does not assume any particular VM - SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error + SendMessage(signedMessage *warp.Message, warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) error } // NewMessageManager constructs a MessageManager for a particular message protocol, defined by the message protocol address and config diff --git a/messages/teleporter/message_manager.go b/messages/teleporter/message_manager.go index d6334bbf..550befcd 100644 --- a/messages/teleporter/message_manager.go +++ b/messages/teleporter/message_manager.go @@ -206,7 +206,8 @@ func (m *messageManager) messageDelivered( // SendMessage extracts the gasLimit and packs the call data to call the receiveCrossChainMessage method of the Teleporter contract, // and dispatches transaction construction and broadcast to the destination client -func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayload []byte, destinationChainID ids.ID) error { +// TODO: Revisit the caching strategy so we can remove parsedVmPayload as a parameter +func (m *messageManager) SendMessage(signedMessage *warp.Message, warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) error { teleporterMessage, ok := m.teleporterMessageCache.Get(signedMessage.ID()) if !ok { m.logger.Debug( @@ -215,7 +216,7 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, parsedVmPayloa zap.String("warpMessageID", signedMessage.ID().String()), ) var err error - teleporterMessage, err = UnpackTeleporterMessage(parsedVmPayload) + teleporterMessage, err = UnpackTeleporterMessage(warpMessageInfo.WarpPayload) if err != nil { m.logger.Error( "Failed unpacking teleporter message.", diff --git a/relayer/message_relayer.go b/relayer/message_relayer.go index b7bf77a5..09a52b51 100644 --- a/relayer/message_relayer.go +++ b/relayer/message_relayer.go @@ -77,6 +77,10 @@ func newMessageRelayer( } func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages.MessageManager) error { + // TODONOW: destinationChainID may be nil/empty, in which case it is an anycast message -> set by subscriber + // ShouldSendMessage makes decisions based ONLY on the message contents, not the relayer config. + // This means that it won't make any decisions about anycase messages based on the relayer config. + // SendMessage handles sending anycast messages to the correct destination chain, based on the relayer config shouldSend, err := messageManager.ShouldSendMessage(r.warpMessageInfo, r.destinationChainID) if err != nil { r.logger.Error( @@ -108,7 +112,8 @@ func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages. // create signed message latency (ms) r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds())) - err = messageManager.SendMessage(signedMessage, r.warpMessageInfo.WarpPayload, r.destinationChainID) + r.logger.Info("DEBUG: About to send message") + err = messageManager.SendMessage(signedMessage, r.warpMessageInfo, r.destinationChainID) if err != nil { r.logger.Error( "Failed to send warp message", @@ -119,7 +124,6 @@ func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages. } r.logger.Info( "Finished relaying message to destination chain", - zap.String("destinationChainID", r.destinationChainID.String()), ) r.incSuccessfulRelayMessageCount() return nil @@ -129,7 +133,6 @@ func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages. func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, error) { r.logger.Info( "Starting relayer routine", - zap.String("destinationChainID", r.destinationChainID.String()), ) sourceChainID := r.warpMessageInfo.WarpUnsignedMessage.SourceChainID @@ -182,7 +185,6 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e if err != nil { r.logger.Error( "Failed to marshal request bytes", - zap.String("destinationChainID", r.destinationChainID.String()), zap.Error(err), ) return nil, err @@ -207,7 +209,6 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e r.logger.Debug( "Relayer collecting signatures from peers.", zap.Int("attempt", attempt), - zap.String("destinationChainID", r.destinationChainID.String()), zap.Int("validatorSetSize", len(validatorSet)), zap.Int("signatureMapSize", len(signatureMap)), zap.Int("responsesExpected", responsesExpected), @@ -318,7 +319,6 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e if err != nil { r.logger.Error( "Failed to aggregate signature.", - zap.String("destinationChainID", r.destinationChainID.String()), zap.Error(err), ) return nil, err @@ -347,7 +347,6 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e if signedMsg != nil { r.logger.Info( "Created signed message.", - zap.String("destinationChainID", r.destinationChainID.String()), ) return signedMsg, nil } @@ -367,7 +366,6 @@ func (r *messageRelayer) createSignedMessage(requestID uint32) (*warp.Message, e r.logger.Warn( "Failed to collect a threshold of signatures", zap.Int("attempts", maxRelayerQueryAttempts), - zap.String("destinationChainID", r.destinationChainID.String()), ) return nil, errNotEnoughSignatures } @@ -383,7 +381,6 @@ func (r *messageRelayer) getCurrentCanonicalValidatorSet() ([]*warp.Validator, u if err != nil { r.logger.Error( "failed to get validating subnet for destination chain", - zap.String("destinationChainID", r.destinationChainID.String()), zap.Error(err), ) return nil, 0, err @@ -459,7 +456,6 @@ func (r *messageRelayer) isValidSignatureResponse( r.logger.Debug( "Response contained an empty signature", zap.String("nodeID", response.NodeID().String()), - zap.String("destinationChainID", r.destinationChainID.String()), ) return blsSignatureBuf{}, false } @@ -468,7 +464,6 @@ func (r *messageRelayer) isValidSignatureResponse( if err != nil { r.logger.Debug( "Failed to create signature from response", - zap.String("destinationChainID", r.destinationChainID.String()), ) return blsSignatureBuf{}, false } @@ -476,7 +471,6 @@ func (r *messageRelayer) isValidSignatureResponse( if !bls.Verify(pubKey, sig, r.warpMessageInfo.WarpUnsignedMessage.Bytes()) { r.logger.Debug( "Failed verification for signature", - zap.String("destinationChainID", r.destinationChainID.String()), ) return blsSignatureBuf{}, false } @@ -518,7 +512,6 @@ func (r *messageRelayer) aggregateSignatures(signatureMap map[int]blsSignatureBu func (r *messageRelayer) incSuccessfulRelayMessageCount() { r.metrics.successfulRelayMessageCount. WithLabelValues( - r.destinationChainID.String(), r.relayer.sourceChainID.String(), r.relayer.sourceSubnetID.String()).Inc() } @@ -526,7 +519,6 @@ func (r *messageRelayer) incSuccessfulRelayMessageCount() { func (r *messageRelayer) incFailedRelayMessageCount(failureReason string) { r.metrics.failedRelayMessageCount. WithLabelValues( - r.destinationChainID.String(), r.relayer.sourceChainID.String(), r.relayer.sourceSubnetID.String(), failureReason).Inc() @@ -535,7 +527,6 @@ func (r *messageRelayer) incFailedRelayMessageCount(failureReason string) { func (r *messageRelayer) setCreateSignedMessageLatencyMS(latency float64) { r.metrics.createSignedMessageLatencyMS. WithLabelValues( - r.destinationChainID.String(), r.relayer.sourceChainID.String(), r.relayer.sourceSubnetID.String()).Set(latency) } diff --git a/relayer/message_relayer_metrics.go b/relayer/message_relayer_metrics.go index 93a6fb89..bfe614a3 100644 --- a/relayer/message_relayer_metrics.go +++ b/relayer/message_relayer_metrics.go @@ -17,7 +17,7 @@ func NewMessageRelayerMetrics(registerer prometheus.Registerer) *MessageRelayerM Name: "successful_relay_message_count", Help: "Number of messages that relayed successfully", }, - []string{"destination_chain_id", "source_chain_id", "source_subnet_id"}, + []string{"source_chain_id", "source_subnet_id"}, ) registerer.MustRegister(successfulRelayMessageCount) @@ -26,7 +26,7 @@ func NewMessageRelayerMetrics(registerer prometheus.Registerer) *MessageRelayerM Name: "create_signed_message_latency_ms", Help: "Latency of creating a signed message in milliseconds", }, - []string{"destination_chain_id", "source_chain_id", "source_subnet_id"}, + []string{"source_chain_id", "source_subnet_id"}, ) registerer.MustRegister(createSignedMessageLatencyMS) @@ -35,7 +35,7 @@ func NewMessageRelayerMetrics(registerer prometheus.Registerer) *MessageRelayerM Name: "failed_relay_message_count", Help: "Number of messages that failed to relay", }, - []string{"destination_chain_id", "source_chain_id", "source_subnet_id", "failure_reason"}, + []string{"source_chain_id", "source_subnet_id", "failure_reason"}, ) registerer.MustRegister(failedRelayMessageCount) diff --git a/relayer/relayer.go b/relayer/relayer.go index 72a9a73c..badeacf0 100644 --- a/relayer/relayer.go +++ b/relayer/relayer.go @@ -194,7 +194,7 @@ func (r *Relayer) RelayMessage(warpMessageInfo *vmtypes.WarpMessageInfo, metrics zap.String("warpMessageID", warpMessageInfo.WarpUnsignedMessage.ID().String()), ) - // Check that the warp message is from a support message protocol contract address. + // Check that the warp message is from a supported message protocol contract address. messageManager, supportedMessageProtocol := r.messageManagers[warpMessageInfo.SourceAddress] if !supportedMessageProtocol { // Do not return an error here because it is expected for there to be messages from other contracts diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 86aa17c8..43026554 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -23,13 +23,11 @@ import ( "github.com/ava-labs/awm-relayer/messages/teleporter" "github.com/ava-labs/awm-relayer/peers" relayerEvm "github.com/ava-labs/awm-relayer/vms/evm" - "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/interfaces" "github.com/ava-labs/subnet-evm/plugin/evm" "github.com/ava-labs/subnet-evm/tests/utils/runner" - teleporter_block_hash "github.com/ava-labs/teleporter/abis/go/teleporter-block-hash" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -537,7 +535,7 @@ var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { relayerCancel context.CancelFunc blockHashReceiverAddressB common.Address subnetAHashes []common.Hash - blockHashABI *abi.ABI + // blockHashABI *abi.ABI ) ginkgo.It("Deploy block hash receiver", ginkgo.Label("Relayer", "DeployBlockHashReceiver"), func() { ctx := context.Background() @@ -568,8 +566,8 @@ var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { // chainBRPCClient, // ) // Expect(err).Should(BeNil()) - blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() - Expect(err).Should(BeNil()) + // blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() + // Expect(err).Should(BeNil()) blockHashReceiverAddressB, err = deriveEVMContractAddress(fundedAddress, nonceB) Expect(err).Should(BeNil()) @@ -773,7 +771,6 @@ var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { // Expect(err).Should(BeNil()) // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - log.Info("event", blockHashABI.Events["ReceiveBlockHash"].ID.String()) //TODONOW remove this logs, err := chainBRPCClient.FilterLogs(ctx, interfaces.FilterQuery{ // BlockHash: &blockHashB, Addresses: []common.Address{blockHashReceiverAddressB}, diff --git a/vms/evm_block_hash/subscriber.go b/vms/evm_block_hash/subscriber.go index 328999b7..ed28f6d7 100644 --- a/vms/evm_block_hash/subscriber.go +++ b/vms/evm_block_hash/subscriber.go @@ -2,7 +2,6 @@ package evm_block_hash import ( "context" - "encoding/json" "errors" "fmt" "math/big" @@ -35,14 +34,13 @@ var ( // subscriber implements Subscriber type subscriber struct { - nodeWSURL string - nodeRPCURL string - chainID ids.ID - logsChan chan vmtypes.WarpMessageInfo - blocks <-chan *types.Header - sub interfaces.Subscription - networkID uint32 - destinationChainIDs []ids.ID + nodeWSURL string + nodeRPCURL string + chainID ids.ID + logsChan chan vmtypes.WarpMessageInfo + blocks <-chan *types.Header + sub interfaces.Subscription + networkID uint32 logger logging.Logger db database.RelayerDatabase @@ -61,54 +59,14 @@ func NewSubscriber(logger logging.Logger, subnetInfo config.SourceSubnet, db dat logs := make(chan vmtypes.WarpMessageInfo, maxClientSubscriptionBuffer) - var destinationChainIDs []ids.ID - // Extract the destination chain IDs from the EVM Block Hash config - for _, cfg := range subnetInfo.MessageContracts { - if config.ParseMessageProtocol(cfg.MessageFormat) == config.BLOCK_HASH_PUBLISHER { - // Marshal the map and unmarshal into the Block Hash Publisher config - data, err := json.Marshal(cfg.Settings) - if err != nil { - logger.Error("Failed to marshal Block Hash Publisher config") - return nil - } - // TODONOW: Don't repeat the block hash config here. Need to resolve dependency cycle - type destinationInfo struct { - ChainID string `json:"chain-id"` - Address string `json:"address"` - Interval string `json:"interval"` - } - - type config struct { - DestinationChains []destinationInfo `json:"destination-chains"` - } - var messageConfig config - if err := json.Unmarshal(data, &messageConfig); err != nil { - logger.Error("Failed to unmarshal Block Hash Publisher config") - return nil - } - for _, chainInfo := range messageConfig.DestinationChains { - chainID, err := ids.FromString(chainInfo.ChainID) - if err != nil { - logger.Error( - "Failed to decode base-58 encoded destination chain ID", - zap.Error(err), - ) - return nil - } - destinationChainIDs = append(destinationChainIDs, chainID) - } - } - } - return &subscriber{ - nodeWSURL: subnetInfo.GetNodeWSEndpoint(), - nodeRPCURL: subnetInfo.GetNodeRPCEndpoint(), - chainID: chainID, - logger: logger, - db: db, - logsChan: logs, - networkID: config.GetNetworkID(), - destinationChainIDs: destinationChainIDs, + nodeWSURL: subnetInfo.GetNodeWSEndpoint(), + nodeRPCURL: subnetInfo.GetNodeRPCEndpoint(), + chainID: chainID, + logger: logger, + db: db, + logsChan: logs, + networkID: config.GetNetworkID(), } } @@ -170,7 +128,7 @@ func (s *subscriber) dialAndSubscribe() error { return nil } -func (s *subscriber) NewWarpMessageInfo(block *types.Header, destinationChainID ids.ID) (*vmtypes.WarpMessageInfo, error) { +func (s *subscriber) NewWarpMessageInfo(block *types.Header) (*vmtypes.WarpMessageInfo, error) { blockHashPayload, err := payload.NewBlockHashPayload(block.Hash()) if err != nil { return nil, err @@ -185,30 +143,29 @@ func (s *subscriber) NewWarpMessageInfo(block *types.Header, destinationChainID } return &vmtypes.WarpMessageInfo{ - DestinationChainID: destinationChainID, - UnsignedMsgBytes: unsignedMessage.Bytes(), - BlockNumber: block.Number.Uint64(), - BlockTimestamp: block.Time, + UnsignedMsgBytes: unsignedMessage.Bytes(), + BlockNumber: block.Number.Uint64(), + BlockTimestamp: block.Time, }, nil } // forward logs from the concrete log channel to the interface channel func (s *subscriber) forwardLogs() { - s.logger.Info("DEBUG Forwarding logs") + // TODO: When publishing anycast messages from the C-Chain, we need to create + // a separate aggregate signature for each destination chain. + // The easiest thing to do would be to fan out here and produce new logs for each of + // the destinations, but that may introduce a cyclic dependency between this package + // and the anycast message protocol package. for block := range s.blocks { - // Fan out to all destination chains - for _, destinationChainID := range s.destinationChainIDs { - s.logger.Info("DEBUG forwarding to destination", zap.String("destinationChainID", destinationChainID.String())) - messageInfo, err := s.NewWarpMessageInfo(block, destinationChainID) - if err != nil { - s.logger.Error( - "Invalid log. Continuing.", - zap.Error(err), - ) - continue - } - s.logsChan <- *messageInfo + messageInfo, err := s.NewWarpMessageInfo(block) + if err != nil { + s.logger.Error( + "Invalid log. Continuing.", + zap.Error(err), + ) + continue } + s.logsChan <- *messageInfo } } From 2f541524a99752f2524ffcbd48f31d21f8a3b03a Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Tue, 3 Oct 2023 22:58:05 +0000 Subject: [PATCH 09/26] add primary network anycast todos --- messages/block_hash_publisher/message_manager.go | 2 ++ relayer/message_relayer.go | 4 ---- relayer/relayer.go | 7 +++++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index 6e61acef..27268074 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -109,6 +109,7 @@ func NewMessageManager( // ShouldSendMessage returns true if the message should be sent to ANY of the configured destination chains // This saves us from having to aggregate signatures in that case. Decisions about which destination chains to send to are made in SendMessage func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageInfo, _ ids.ID) (bool, error) { + // TODO: Handle the primary network case. If it's the primary network, only check the passed in destinationChainID for _, destination := range m.destinations { if destination.shouldSend(warpMessageInfo.BlockTimestamp, warpMessageInfo.BlockNumber) { return true, nil @@ -118,6 +119,7 @@ func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageI } func (m *messageManager) SendMessage(signedMessage *warp.Message, warpMessageInfo *vmtypes.WarpMessageInfo, _ ids.ID) error { + // TODO: Handle the primary network case. If it's the primary network, only send to the passed in destinationChainID m.logger.Info( "DEBUG SENDING", zap.String("destinationInfo", fmt.Sprintf("%#v", m.destinations)), diff --git a/relayer/message_relayer.go b/relayer/message_relayer.go index 09a52b51..755f275a 100644 --- a/relayer/message_relayer.go +++ b/relayer/message_relayer.go @@ -77,10 +77,6 @@ func newMessageRelayer( } func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages.MessageManager) error { - // TODONOW: destinationChainID may be nil/empty, in which case it is an anycast message -> set by subscriber - // ShouldSendMessage makes decisions based ONLY on the message contents, not the relayer config. - // This means that it won't make any decisions about anycase messages based on the relayer config. - // SendMessage handles sending anycast messages to the correct destination chain, based on the relayer config shouldSend, err := messageManager.ShouldSendMessage(r.warpMessageInfo, r.destinationChainID) if err != nil { r.logger.Error( diff --git a/relayer/relayer.go b/relayer/relayer.go index badeacf0..87f9cc2f 100644 --- a/relayer/relayer.go +++ b/relayer/relayer.go @@ -72,6 +72,7 @@ func NewRelayer( messageManagers := make(map[common.Hash]messages.MessageManager) for address, config := range sourceSubnetInfo.MessageContracts { addressHash := common.HexToHash(address) + // TODO: To handle the primary network case, the messageManager needs to know if its source subnet is the primary network messageManager, err := messages.NewMessageManager(logger, addressHash, config, destinationClients) if err != nil { logger.Error( @@ -206,6 +207,12 @@ func (r *Relayer) RelayMessage(warpMessageInfo *vmtypes.WarpMessageInfo, metrics return nil } + // TODO: To handle anycasting for the primary network, we need to call newMessageRelayer in a loop for each of the + // configured destinations. We can get the configured destinations from messageManage. + // This is necessary because in the primary network case, each destination will have a different aggregate signature. + // For anycasting from any other network, we only need to create a single aggregate signature, so we fan out in + // messageManager instead. + // Create and run the message relayer to attempt to deliver the message to the destination chain messageRelayer := newMessageRelayer(r.logger, metrics, r, warpMessageInfo, warpMessageInfo.DestinationChainID, r.responseChan, messageCreator) if err != nil { From 76478cce5bc83bf931369c9499fded4ed0e086c9 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Wed, 4 Oct 2023 21:50:45 +0000 Subject: [PATCH 10/26] filter logs --- tests/e2e_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 43026554..907ac126 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -23,11 +23,13 @@ import ( "github.com/ava-labs/awm-relayer/messages/teleporter" "github.com/ava-labs/awm-relayer/peers" relayerEvm "github.com/ava-labs/awm-relayer/vms/evm" + "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/interfaces" "github.com/ava-labs/subnet-evm/plugin/evm" "github.com/ava-labs/subnet-evm/tests/utils/runner" + teleporter_block_hash "github.com/ava-labs/teleporter/abis/go/teleporter-block-hash" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/crypto" @@ -535,7 +537,7 @@ var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { relayerCancel context.CancelFunc blockHashReceiverAddressB common.Address subnetAHashes []common.Hash - // blockHashABI *abi.ABI + blockHashABI *abi.ABI ) ginkgo.It("Deploy block hash receiver", ginkgo.Label("Relayer", "DeployBlockHashReceiver"), func() { ctx := context.Background() @@ -566,8 +568,8 @@ var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { // chainBRPCClient, // ) // Expect(err).Should(BeNil()) - // blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() - // Expect(err).Should(BeNil()) + blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() + Expect(err).Should(BeNil()) blockHashReceiverAddressB, err = deriveEVMContractAddress(fundedAddress, nonceB) Expect(err).Should(BeNil()) @@ -720,11 +722,9 @@ var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { defer subB.Unsubscribe() // TODONOW: not necessarily true, since the block height might be < 5 - // Send 6 transactions to produce 6 blocks on subnet A - // We expect the first and the sixth txs to be published by the relayer, since the relayer - // will be initialized with the "latest published block" to be height 0 - - for i := 0; i < 6; i++ { + // Send 5 transactions to produce 5 blocks on subnet A + // We expect at exactly one of the block hashes to be published by the relayer + for i := 0; i < 5; i++ { value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1)) // 1eth txA := types.NewTx(&types.DynamicFeeTx{ ChainID: chainAIDInt, @@ -772,13 +772,13 @@ var _ = ginkgo.Describe("[Relayer Publish Block Hash]", ginkgo.Ordered, func() { // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) logs, err := chainBRPCClient.FilterLogs(ctx, interfaces.FilterQuery{ - // BlockHash: &blockHashB, + BlockHash: &blockHashB, Addresses: []common.Address{blockHashReceiverAddressB}, - // Topics: [][]common.Hash{ - // { - // blockHashABI.Events["ReceiveBlockHash"].ID, - // }, - // }, + Topics: [][]common.Hash{ + { + blockHashABI.Events["ReceiveBlockHash"].ID, + }, + }, }) Expect(err).Should(BeNil()) log.Info("Logs", "logs", logs) From f95948f3b46879ac8bf1cf8e82d7345f96bee5a8 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 14:22:31 +0000 Subject: [PATCH 11/26] go mod tidy --- go.mod | 2 +- go.sum | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 30e5b15b..a8790c46 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +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-20230928142515-43ad4b3620af github.com/ethereum/go-ethereum v1.12.0 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 @@ -21,7 +22,6 @@ require ( require ( github.com/Microsoft/go-winio v0.5.2 // indirect github.com/ava-labs/coreth v0.12.5-rc.6 // indirect - github.com/ava-labs/teleporter v0.0.0-20230928142515-43ad4b3620af // indirect github.com/cockroachdb/errors v1.9.1 // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect github.com/cockroachdb/pebble v0.0.0-20230209160836-829675f94811 // indirect diff --git a/go.sum b/go.sum index 74e12c66..2f70e62a 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,6 @@ 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-20230927213446-c08725fa0c66 h1:x0sfz1yv4wv6u33usI+rDbGND/Rc2cbw5D+MvcOAD8c= -github.com/ava-labs/teleporter v0.0.0-20230927213446-c08725fa0c66/go.mod h1:ULmbg6e2iiTuj2ac9Ru53sV2ngAsX6csZSKesIPE6lU= github.com/ava-labs/teleporter v0.0.0-20230928142515-43ad4b3620af h1:OazJMJ2aMkOVuj6MfRHZ3d/n2RbedMzMo6nGHHoj09k= github.com/ava-labs/teleporter v0.0.0-20230928142515-43ad4b3620af/go.mod h1:ULmbg6e2iiTuj2ac9Ru53sV2ngAsX6csZSKesIPE6lU= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= From e12913ca80130433d81c1c7f47629109ef277d42 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 14:24:26 +0000 Subject: [PATCH 12/26] latest teleporter --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a8790c46..f817732d 100644 --- a/go.mod +++ b/go.mod @@ -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-20230928142515-43ad4b3620af + github.com/ava-labs/teleporter v0.0.0-20231005141349-feb8fe1523b6 github.com/ethereum/go-ethereum v1.12.0 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 diff --git a/go.sum b/go.sum index 2f70e62a..177620ad 100644 --- a/go.sum +++ b/go.sum @@ -69,8 +69,8 @@ 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-20230928142515-43ad4b3620af h1:OazJMJ2aMkOVuj6MfRHZ3d/n2RbedMzMo6nGHHoj09k= -github.com/ava-labs/teleporter v0.0.0-20230928142515-43ad4b3620af/go.mod h1:ULmbg6e2iiTuj2ac9Ru53sV2ngAsX6csZSKesIPE6lU= +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/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= From ee1d540c63d82ca8e9658dfc3e0492ebd9ea96f6 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 14:58:12 +0000 Subject: [PATCH 13/26] block hash test case to own file --- tests/e2e_test.go | 4 + tests/publish_block_hashes.go | 336 ++++++++++++++++++++++++++++++++++ 2 files changed, 340 insertions(+) create mode 100644 tests/publish_block_hashes.go diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 907ac126..46702324 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -291,6 +291,10 @@ var _ = ginkgo.AfterSuite(func() { Expect(os.Remove(relayerConfigPath)).Should(BeNil()) }) +var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { + ginkgo.It("Publish Block Hashes", PublishBlockHashes) +}) + // Ginkgo describe node that acts as a container for the relayer e2e tests. This test suite // will run in order, starting off by setting up the subnet URIs and creating a relayer config // file. It will then build the relayer binary and run it with the config file. The relayer diff --git a/tests/publish_block_hashes.go b/tests/publish_block_hashes.go new file mode 100644 index 00000000..fdfcffb3 --- /dev/null +++ b/tests/publish_block_hashes.go @@ -0,0 +1,336 @@ +package tests + +import ( + "bufio" + "context" + "encoding/hex" + "encoding/json" + "fmt" + "math/big" + "os" + "os/exec" + "time" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/awm-relayer/config" + "github.com/ava-labs/awm-relayer/peers" + relayerEvm "github.com/ava-labs/awm-relayer/vms/evm" + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/interfaces" + teleporter_block_hash "github.com/ava-labs/teleporter/abis/go/teleporter-block-hash" + teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/log" + . "github.com/onsi/gomega" +) + +func PublishBlockHashes() { + var ( + relayerCmd *exec.Cmd + relayerCancel context.CancelFunc + blockHashReceiverAddressB common.Address + subnetAHashes []common.Hash + blockHashABI *abi.ABI + ) + + subnetAInfo := teleporterTestUtils.GetSubnetATestInfo() + subnetBInfo := teleporterTestUtils.GetSubnetBTestInfo() + fundedAddress, fundedKey := teleporterTestUtils.GetFundedAccountInfo() + + // + // Deploy block hash receiver on Subnet B + // + ctx := context.Background() + blockHashReceiverByteCode := readHexTextFile("./tests/BlockHashReceiverByteCode.txt") + + nonceB, err := subnetBInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) + Expect(err).Should(BeNil()) + + // gasTipCapA, err := subnetAInfo.ChainWSClient.SuggestGasTipCap(context.Background()) + // Expect(err).Should(BeNil()) + + // baseFeeA, err := subnetAInfo.ChainWSClient.EstimateBaseFee(context.Background()) + // Expect(err).Should(BeNil()) + // gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) + // gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) + + // contractAuth, err := bind.NewKeyedTransactorWithChainID(fundedKey, big.NewInt(peers.LocalNetworkID)) + // contractAuth.Nonce = big.NewInt(int64(nonceA)) + // contractAuth.GasFeeCap = gasFeeCapA + // contractAuth.GasTipCap = gasTipCapA + // contractAuth.GasLimit = 1000000 + + // Expect(err).Should(BeNil()) + // blockHashReceiverAddressB, _, _, err = bind.DeployContract( + // contractAuth, + // *blockHashABI, + // common.FromHex(blockHashReceiverByteCode), + // subnetBInfo.ChainWSClient, + // ) + // Expect(err).Should(BeNil()) + blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() + Expect(err).Should(BeNil()) + blockHashReceiverAddressB, err = deriveEVMContractAddress(fundedAddress, nonceB) + Expect(err).Should(BeNil()) + + cmdOutput := make(chan string) + cmd := exec.Command( + "cast", + "send", + "--rpc-url", subnetBInfo.ChainWSURI, + "--private-key", hexutil.Encode(fundedKey.D.Bytes()), + "--create", blockHashReceiverByteCode, + ) + + // Set up a pipe to capture the command's output + cmdReader, _ := cmd.StdoutPipe() + + // Start a goroutine to read and output the command's stdout + go func() { + scanner := bufio.NewScanner(cmdReader) + for scanner.Scan() { + log.Info(scanner.Text()) + } + cmdOutput <- "Command execution finished" + }() + + err = cmd.Run() + Expect(err).Should(BeNil()) + + time.Sleep(5 * time.Second) + deployedCode, err := subnetBInfo.ChainWSClient.CodeAt(ctx, blockHashReceiverAddressB, nil) + Expect(err).Should(BeNil()) + Expect(len(deployedCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode + + log.Info("Deployed block hash receiver contract", "address", blockHashReceiverAddressB.Hex()) + + // + // Setup relayer config + // + hostA, portA, err := getURIHostAndPort(subnetAInfo.ChainNodeURIs[0]) + Expect(err).Should(BeNil()) + + hostB, portB, err := getURIHostAndPort(subnetBInfo.ChainNodeURIs[0]) + Expect(err).Should(BeNil()) + + log.Info( + "Setting up relayer config", + "hostA", hostA, + "portA", portA, + "blockChainA", subnetAInfo.BlockchainID.String(), + "hostB", hostB, + "portB", portB, + "blockChainB", subnetBInfo.BlockchainID.String(), + "subnetA", subnetAInfo.SubnetID.String(), + "subnetB", subnetBInfo.SubnetID.String(), + ) + + relayerConfig := config.Config{ + LogLevel: logging.Info.LowerString(), + NetworkID: peers.LocalNetworkID, + PChainAPIURL: subnetAInfo.ChainNodeURIs[0], + EncryptConnection: false, + StorageLocation: storageLocation, + SourceSubnets: []config.SourceSubnet{ + { + SubnetID: subnetAInfo.SubnetID.String(), + ChainID: subnetAInfo.BlockchainID.String(), + VM: config.EVM_BLOCKHASH.String(), + EncryptConnection: false, + APINodeHost: hostA, + APINodePort: portA, + MessageContracts: map[string]config.MessageProtocolConfig{ + "0x0000000000000000000000000000000000000000": { + MessageFormat: config.BLOCK_HASH_PUBLISHER.String(), + Settings: map[string]interface{}{ + "destination-chains": []struct { + ChainID string `json:"chain-id"` + Address string `json:"address"` + Interval string `json:"interval"` + }{ + { + ChainID: subnetBInfo.BlockchainID.String(), + Address: blockHashReceiverAddressB.String(), + Interval: "5", + }, + }, + }, + }, + }, + }, + }, + DestinationSubnets: []config.DestinationSubnet{ + { + SubnetID: subnetBInfo.SubnetID.String(), + ChainID: subnetBInfo.BlockchainID.String(), + VM: config.EVM.String(), + EncryptConnection: false, + APINodeHost: hostB, + APINodePort: portB, + AccountPrivateKey: hex.EncodeToString(fundedKey.D.Bytes()), + }, + }, + } + + data, err := json.MarshalIndent(relayerConfig, "", "\t") + Expect(err).Should(BeNil()) + + f, err := os.CreateTemp(os.TempDir(), "relayer-config.json") + Expect(err).Should(BeNil()) + + _, err = f.Write(data) + Expect(err).Should(BeNil()) + relayerConfigPath := f.Name() + + log.Info("Created awm-relayer config", "configPath", relayerConfigPath, "config", string(data)) + + // + // Build Relayer + // + // Build the awm-relayer binary + cmd = exec.Command("./scripts/build.sh") + out, err := cmd.CombinedOutput() + fmt.Println(string(out)) + Expect(err).Should(BeNil()) + + // + // Publish block hashes + // + relayerCmd, relayerCancel = runRelayerExecutable(ctx) + nonceA, err := subnetAInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) + Expect(err).Should(BeNil()) + + destinationAddress := common.HexToAddress("0x0000000000000000000000000000000000000000") + gasTipCapA, err := subnetAInfo.ChainWSClient.SuggestGasTipCap(context.Background()) + Expect(err).Should(BeNil()) + + baseFeeA, err := subnetAInfo.ChainWSClient.EstimateBaseFee(context.Background()) + Expect(err).Should(BeNil()) + gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) + gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) + + // Subscribe to the destination chain block published + newHeadsA := make(chan *types.Header, 10) + subA, err := subnetAInfo.ChainWSClient.SubscribeNewHead(ctx, newHeadsA) + Expect(err).Should(BeNil()) + defer subA.Unsubscribe() + + // Subscribe to the destination chain block published + newHeadsB := make(chan *types.Header, 10) + subB, err := subnetBInfo.ChainWSClient.SubscribeNewHead(ctx, newHeadsB) + Expect(err).Should(BeNil()) + defer subB.Unsubscribe() + + // TODONOW: not necessarily true, since the block height might be < 5 + // Send 5 transactions to produce 5 blocks on subnet A + // We expect at exactly one of the block hashes to be published by the relayer + for i := 0; i < 5; i++ { + value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1)) // 1eth + txA := types.NewTx(&types.DynamicFeeTx{ + ChainID: subnetAInfo.ChainIDInt, + Nonce: nonceA + uint64(i), + To: &destinationAddress, + Gas: defaultTeleporterMessageGas, + GasFeeCap: gasFeeCapA, + GasTipCap: gasTipCapA, + Value: value, + }) + txSignerA := types.LatestSignerForChainID(subnetAInfo.ChainIDInt) + triggerTxA, err := types.SignTx(txA, txSignerA, fundedKey) + Expect(err).Should(BeNil()) + err = subnetAInfo.ChainWSClient.SendTransaction(ctx, triggerTxA) + Expect(err).Should(BeNil()) + + log.Info("Waiting for new block confirmation", "block", i) + newHeadA := <-newHeadsA + subnetAHashes = append(subnetAHashes, newHeadA.Hash()) + } + + time.Sleep(5 * time.Second) + + for { + newHeadB := <-newHeadsB + log.Info("Fetching log from the newly produced block") + + blockHashB := newHeadB.Hash() + + block, err := subnetBInfo.ChainWSClient.BlockByHash(ctx, blockHashB) + Expect(err).Should(BeNil()) + txs := block.Transactions() + log.Info(fmt.Sprintf("numTxs: %d", len(txs))) + for _, tx := range txs { + log.Info(fmt.Sprintf("txHash: %s", tx.Hash().String())) + log.Info(fmt.Sprintf("to: %s", tx.To().String())) + log.Info(fmt.Sprintf("data: %s", hex.EncodeToString(tx.Data()))) + receipt, err := subnetBInfo.ChainWSClient.TransactionReceipt(ctx, tx.Hash()) + Expect(err).Should(BeNil()) + Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + } + + // receipt, err := subnetBInfo.ChainWSClient.TransactionReceipt(ctx, blockHashB) + // Expect(err).Should(BeNil()) + // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + + logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ + BlockHash: &blockHashB, + Addresses: []common.Address{blockHashReceiverAddressB}, + Topics: [][]common.Hash{ + { + blockHashABI.Events["ReceiveBlockHash"].ID, + }, + }, + }) + Expect(err).Should(BeNil()) + log.Info("Logs", "logs", logs) + } + // // Wait for new blocks on B. We are expecting 2 new blocks + // { + // newHeadB := <-newHeadsB + // blockHashB := newHeadB.Hash() + + // log.Info("Fetching relevant warp logs from the newly produced block") + // logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ + // BlockHash: &blockHashB, + // // Addresses: []common.Address{blockHashReceiverAddressB}, + // // Topics: [][]common.Hash{ + // // { + // // blockHashABI.Events["ReceiveBlockHash"].ID, + // // }, + // // }, + // }) + // log.Info("Logs", "logs", logs) + // Expect(err).Should(BeNil()) + // Expect(len(logs)).Should(Equal(1)) + + // Expect(logs[0].Topics[2]).Should(Equal(subnetAHashes[0])) + // } + // { + // newHeadB := <-newHeadsB + // blockHashB := newHeadB.Hash() + + // log.Info("Fetching relevant warp logs from the newly produced block") + // logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ + // BlockHash: &blockHashB, + // Addresses: []common.Address{blockHashReceiverAddressB}, + // Topics: [][]common.Hash{ + // { + // blockHashABI.Events["ReceiveBlockHash"].ID, + // }, + // }, + // }) + // Expect(err).Should(BeNil()) + // Expect(len(logs)).Should(Equal(1)) + + // Expect(logs[0].Topics[2]).Should(Equal(subnetAHashes[5])) + // } + + // Cancel the command and stop the relayer + relayerCancel() + _ = relayerCmd.Wait() + + // + // Verify received block hash + // +} From 10ff86d74d4bdbb7ad3fc5f66add4285cbd905f2 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 19:28:02 +0000 Subject: [PATCH 14/26] properly set last published block --- .../block_hash_publisher/message_manager.go | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index 27268074..e688537a 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -2,7 +2,6 @@ package block_hash_publisher import ( "encoding/json" - "fmt" "time" "github.com/ava-labs/avalanchego/ids" @@ -17,7 +16,7 @@ import ( ) const ( - publishBlockHashGasLimit = 1000000 // TODONOW: set the correct gas limit + publishBlockHashGasLimit = 275000 ) type destinationSenderInfo struct { @@ -28,10 +27,8 @@ type destinationSenderInfo struct { timeIntervalSeconds uint64 blockInterval uint64 - lastApprovedTime uint64 - lastTimeSent uint64 - lastApprovedBlock uint64 - lastBlock uint64 + lastTimeSent uint64 + lastBlock uint64 } func (d *destinationSenderInfo) shouldSend(blockTimestamp uint64, blockNumber uint64) bool { @@ -79,7 +76,6 @@ func NewMessageManager( ) return nil, err } - logger.Info("DEBUG CONFIG", zap.String("config", fmt.Sprintf("%#v", messageConfig))) destinations := make(map[ids.ID]*destinationSenderInfo) for _, destination := range messageConfig.DestinationChains { @@ -120,14 +116,13 @@ func (m *messageManager) ShouldSendMessage(warpMessageInfo *vmtypes.WarpMessageI func (m *messageManager) SendMessage(signedMessage *warp.Message, warpMessageInfo *vmtypes.WarpMessageInfo, _ ids.ID) error { // TODO: Handle the primary network case. If it's the primary network, only send to the passed in destinationChainID - m.logger.Info( - "DEBUG SENDING", - zap.String("destinationInfo", fmt.Sprintf("%#v", m.destinations)), - zap.Uint64("blockTimestampe", warpMessageInfo.BlockTimestamp), - zap.Uint64("blockNumber", warpMessageInfo.BlockNumber), - ) for destinationChainID, destination := range m.destinations { if !destination.shouldSend(warpMessageInfo.BlockTimestamp, warpMessageInfo.BlockNumber) { + m.logger.Debug( + "Not sending message to destination chain", + zap.String("destinationChainID", destinationChainID.String()), + zap.String("warpMessageID", signedMessage.ID().String()), + ) continue } @@ -166,8 +161,8 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, warpMessageInf } // Set the last sent block/time - destination.lastTimeSent = destination.lastApprovedTime - destination.lastBlock = destination.lastApprovedBlock + destination.lastTimeSent = warpMessageInfo.BlockTimestamp + destination.lastBlock = warpMessageInfo.BlockNumber m.logger.Info( "Sent message to destination chain", zap.String("destinationChainID", destinationChainID.String()), From a3365cb99ef97d1dc93a76425ec4c3a280860df0 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 19:28:36 +0000 Subject: [PATCH 15/26] cleanup --- config/config.go | 1 - relayer/message_relayer.go | 1 - 2 files changed, 2 deletions(-) diff --git a/config/config.go b/config/config.go index 01b3a547..483b08d2 100644 --- a/config/config.go +++ b/config/config.go @@ -239,7 +239,6 @@ func (s *SourceSubnet) Validate() error { } case EVM_BLOCKHASH: // No additional validation required - // TODONOW: we shouldn't require an address as the key for block hash publisher default: return fmt.Errorf("unsupported VM type for source subnet: %v", s.VM) } diff --git a/relayer/message_relayer.go b/relayer/message_relayer.go index 755f275a..ed1eb5cb 100644 --- a/relayer/message_relayer.go +++ b/relayer/message_relayer.go @@ -108,7 +108,6 @@ func (r *messageRelayer) relayMessage(requestID uint32, messageManager messages. // create signed message latency (ms) r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds())) - r.logger.Info("DEBUG: About to send message") err = messageManager.SendMessage(signedMessage, r.warpMessageInfo, r.destinationChainID) if err != nil { r.logger.Error( From a70c6ed024ec10dd5b513dc3a58bc5c44d45fb10 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 19:29:03 +0000 Subject: [PATCH 16/26] move block hash bytecode --- tests/{ => utils}/BlockHashReceiverByteCode.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{ => utils}/BlockHashReceiverByteCode.txt (100%) diff --git a/tests/BlockHashReceiverByteCode.txt b/tests/utils/BlockHashReceiverByteCode.txt similarity index 100% rename from tests/BlockHashReceiverByteCode.txt rename to tests/utils/BlockHashReceiverByteCode.txt From ee17f913611ffeb4b340841a46da82dd6695b979 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 19:29:19 +0000 Subject: [PATCH 17/26] clean up e2e test --- tests/e2e_test.go | 2 +- tests/publish_block_hashes.go | 162 ++++++++++------------------------ 2 files changed, 46 insertions(+), 118 deletions(-) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 04b7c710..397c207f 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -35,7 +35,7 @@ var _ = ginkgo.BeforeSuite(setupSuite) var _ = ginkgo.AfterSuite(teleporterTestUtils.TearDownNetwork) var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { - ginkgo.It("Basic Relay", BasicRelay) + // ginkgo.It("Basic Relay", BasicRelay) ginkgo.It("Publish Block Hashes", PublishBlockHashes) }) diff --git a/tests/publish_block_hashes.go b/tests/publish_block_hashes.go index 5d6541f7..ea4c50de 100644 --- a/tests/publish_block_hashes.go +++ b/tests/publish_block_hashes.go @@ -46,33 +46,12 @@ func PublishBlockHashes() { // Deploy block hash receiver on Subnet B // ctx := context.Background() - blockHashReceiverByteCode := testUtils.ReadHexTextFile("./tests/BlockHashReceiverByteCode.txt") + + blockHashReceiverByteCode := testUtils.ReadHexTextFile("./tests/utils/BlockHashReceiverByteCode.txt") nonceB, err := subnetBInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) Expect(err).Should(BeNil()) - // gasTipCapA, err := subnetAInfo.ChainWSClient.SuggestGasTipCap(context.Background()) - // Expect(err).Should(BeNil()) - - // baseFeeA, err := subnetAInfo.ChainWSClient.EstimateBaseFee(context.Background()) - // Expect(err).Should(BeNil()) - // gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) - // gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) - - // contractAuth, err := bind.NewKeyedTransactorWithChainID(fundedKey, big.NewInt(peers.LocalNetworkID)) - // contractAuth.Nonce = big.NewInt(int64(nonceA)) - // contractAuth.GasFeeCap = gasFeeCapA - // contractAuth.GasTipCap = gasTipCapA - // contractAuth.GasLimit = 1000000 - - // Expect(err).Should(BeNil()) - // blockHashReceiverAddressB, _, _, err = bind.DeployContract( - // contractAuth, - // *blockHashABI, - // common.FromHex(blockHashReceiverByteCode), - // subnetBInfo.ChainWSClient, - // ) - // Expect(err).Should(BeNil()) blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() Expect(err).Should(BeNil()) blockHashReceiverAddressB, err = deploymentUtils.DeriveEVMContractAddress(fundedAddress, nonceB) @@ -112,7 +91,7 @@ func PublishBlockHashes() { err = cmd.Run() Expect(err).Should(BeNil()) - time.Sleep(5 * time.Second) + // Confirm successful deployment deployedCode, err := subnetBInfo.ChainWSClient.CodeAt(ctx, blockHashReceiverAddressB, nil) Expect(err).Should(BeNil()) Expect(len(deployedCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode @@ -202,7 +181,6 @@ func PublishBlockHashes() { // // Build Relayer // - // Build the awm-relayer binary cmd = exec.Command("./scripts/build.sh") out, err := cmd.CombinedOutput() fmt.Println(string(out)) @@ -212,8 +190,6 @@ func PublishBlockHashes() { // Publish block hashes // relayerCmd, relayerCancel = testUtils.RunRelayerExecutable(ctx, relayerConfigPath) - nonceA, err := subnetAInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) - Expect(err).Should(BeNil()) destinationAddress := common.HexToAddress("0x0000000000000000000000000000000000000000") gasTipCapA, err := subnetAInfo.ChainWSClient.SuggestGasTipCap(context.Background()) @@ -224,12 +200,6 @@ func PublishBlockHashes() { gasFeeCapA := baseFeeA.Mul(baseFeeA, big.NewInt(relayerEvm.BaseFeeFactor)) gasFeeCapA.Add(gasFeeCapA, big.NewInt(relayerEvm.MaxPriorityFeePerGas)) - // Subscribe to the destination chain block published - newHeadsA := make(chan *types.Header, 10) - subA, err := subnetAInfo.ChainWSClient.SubscribeNewHead(ctx, newHeadsA) - Expect(err).Should(BeNil()) - defer subA.Unsubscribe() - // Subscribe to the destination chain block published newHeadsB := make(chan *types.Header, 10) subB, err := subnetBInfo.ChainWSClient.SubscribeNewHead(ctx, newHeadsB) @@ -237,13 +207,14 @@ func PublishBlockHashes() { defer subB.Unsubscribe() // Send 5 transactions to produce 5 blocks on subnet A - // We expect at exactly one of the block hashes to be published by the relayer + // We expect exactly one of the block hashes to be published by the relayer for i := 0; i < 5; i++ { - // TODONOW: Utilize teleporterTestUtils tx sending/signing utils + nonceA, err := subnetAInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) + Expect(err).Should(BeNil()) value := big.NewInt(0).Mul(big.NewInt(1e18), big.NewInt(1)) // 1eth txA := types.NewTx(&types.DynamicFeeTx{ ChainID: subnetAInfo.ChainIDInt, - Nonce: nonceA + uint64(i), + Nonce: nonceA, To: &destinationAddress, Gas: teleporterTestUtils.DefaultTeleporterTransactionGas, GasFeeCap: gasFeeCapA, @@ -251,99 +222,56 @@ func PublishBlockHashes() { Value: value, }) txSignerA := types.LatestSignerForChainID(subnetAInfo.ChainIDInt) + triggerTxA, err := types.SignTx(txA, txSignerA, fundedKey) Expect(err).Should(BeNil()) - err = subnetAInfo.ChainWSClient.SendTransaction(ctx, triggerTxA) - Expect(err).Should(BeNil()) - log.Info("Waiting for new block confirmation", "block", i) - newHeadA := <-newHeadsA - subnetAHashes = append(subnetAHashes, newHeadA.Hash()) - } + receipt := teleporterTestUtils.SendTransactionAndWaitForAcceptance(ctx, subnetAInfo.ChainWSClient, triggerTxA) - time.Sleep(5 * time.Second) + log.Info("Sent block on destination", "blockHash", receipt.BlockHash) + subnetAHashes = append(subnetAHashes, receipt.BlockHash) + } - for { - newHeadB := <-newHeadsB - log.Info("Fetching log from the newly produced block") + // Listen on the destination chain for the published block hash + newHeadB := <-newHeadsB + log.Info("Fetching log from the newly produced block") - blockHashB := newHeadB.Hash() + blockHashB := newHeadB.Hash() - block, err := subnetBInfo.ChainWSClient.BlockByHash(ctx, blockHashB) - Expect(err).Should(BeNil()) - txs := block.Transactions() - log.Info(fmt.Sprintf("numTxs: %d", len(txs))) - for _, tx := range txs { - log.Info(fmt.Sprintf("txHash: %s", tx.Hash().String())) - log.Info(fmt.Sprintf("to: %s", tx.To().String())) - log.Info(fmt.Sprintf("data: %s", hex.EncodeToString(tx.Data()))) - receipt, err := subnetBInfo.ChainWSClient.TransactionReceipt(ctx, tx.Hash()) - Expect(err).Should(BeNil()) - Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) - } + logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ + BlockHash: &blockHashB, + Addresses: []common.Address{blockHashReceiverAddressB}, + Topics: [][]common.Hash{ + { + blockHashABI.Events["ReceiveBlockHash"].ID, + }, + }, + }) + Expect(err).Should(BeNil()) - // receipt, err := subnetBInfo.ChainWSClient.TransactionReceipt(ctx, blockHashB) - // Expect(err).Should(BeNil()) - // Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) + bind, err := teleporter_block_hash.NewTeleporterBlockHash(blockHashReceiverAddressB, subnetBInfo.ChainWSClient) + Expect(err).Should(BeNil()) + event, err := bind.ParseReceiveBlockHash(logs[0]) + Expect(err).Should(BeNil()) - logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ - BlockHash: &blockHashB, - Addresses: []common.Address{blockHashReceiverAddressB}, - Topics: [][]common.Hash{ - { - blockHashABI.Events["ReceiveBlockHash"].ID, - }, - }, - }) - Expect(err).Should(BeNil()) - log.Info("Logs", "logs", logs) + // The published block hash should match one of the ones sent on Subnet A + foundHash := false + for _, blockHash := range subnetAHashes { + if hex.EncodeToString(blockHash[:]) == hex.EncodeToString(event.BlockHash[:]) { + foundHash = true + break + } } - // // Wait for new blocks on B. We are expecting 2 new blocks - // { - // newHeadB := <-newHeadsB - // blockHashB := newHeadB.Hash() - - // log.Info("Fetching relevant warp logs from the newly produced block") - // logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ - // BlockHash: &blockHashB, - // // Addresses: []common.Address{blockHashReceiverAddressB}, - // // Topics: [][]common.Hash{ - // // { - // // blockHashABI.Events["ReceiveBlockHash"].ID, - // // }, - // // }, - // }) - // log.Info("Logs", "logs", logs) - // Expect(err).Should(BeNil()) - // Expect(len(logs)).Should(Equal(1)) - - // Expect(logs[0].Topics[2]).Should(Equal(subnetAHashes[0])) - // } - // { - // newHeadB := <-newHeadsB - // blockHashB := newHeadB.Hash() - - // log.Info("Fetching relevant warp logs from the newly produced block") - // logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ - // BlockHash: &blockHashB, - // Addresses: []common.Address{blockHashReceiverAddressB}, - // Topics: [][]common.Hash{ - // { - // blockHashABI.Events["ReceiveBlockHash"].ID, - // }, - // }, - // }) - // Expect(err).Should(BeNil()) - // Expect(len(logs)).Should(Equal(1)) - - // Expect(logs[0].Topics[2]).Should(Equal(subnetAHashes[5])) - // } + if !foundHash { + Expect(false).Should(BeTrue(), "published block hash does not match any of the sent block hashes") + } + log.Info("Received published block hash on destination", "blockHash", hex.EncodeToString(event.BlockHash[:])) + + // We shouldn't receive any more blocks, since the relayer is configured to publish once every 5 blocks on the source + log.Info("Waiting for 10s to ensure no new block confirmations on destination chain") + Consistently(newHeadsB, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive()) // Cancel the command and stop the relayer relayerCancel() _ = relayerCmd.Wait() - - // - // Verify received block hash - // } From 1e40f981c602ae16b448e79f3816235a2dc4fc8a Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 19:48:34 +0000 Subject: [PATCH 18/26] use updated block hash abi --- go.mod | 2 +- go.sum | 2 ++ messages/block_hash_publisher/message_manager.go | 2 +- tests/publish_block_hashes.go | 6 +++--- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index f817732d..9fa368b1 100644 --- a/go.mod +++ b/go.mod @@ -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-20231005141349-feb8fe1523b6 + github.com/ava-labs/teleporter v0.0.0-20231005194159-a8e949bd37c9 github.com/ethereum/go-ethereum v1.12.0 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 diff --git a/go.sum b/go.sum index 177620ad..419775a7 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,8 @@ github.com/ava-labs/subnet-evm v0.5.6 h1:u+xBvEExOa362Up02hgSgeF+aqDona57whhRIeE github.com/ava-labs/subnet-evm v0.5.6/go.mod h1:desGY3ghT+Ner+oqxeovwF37eM4dmMQbYZECONPQU9w= 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/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= diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index e688537a..aa0f2ac1 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/vms" "github.com/ava-labs/awm-relayer/vms/vmtypes" - teleporter_block_hash "github.com/ava-labs/teleporter/abis/go/teleporter-block-hash" + teleporter_block_hash "github.com/ava-labs/teleporter/abis/TeleporterBlockHashReceiver" "github.com/ethereum/go-ethereum/common" "go.uber.org/zap" ) diff --git a/tests/publish_block_hashes.go b/tests/publish_block_hashes.go index ea4c50de..faab73de 100644 --- a/tests/publish_block_hashes.go +++ b/tests/publish_block_hashes.go @@ -19,7 +19,7 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/interfaces" - teleporter_block_hash "github.com/ava-labs/teleporter/abis/go/teleporter-block-hash" + teleporter_block_hash "github.com/ava-labs/teleporter/abis/TeleporterBlockHashReceiver" deploymentUtils "github.com/ava-labs/teleporter/contract-deployment/utils" teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils" "github.com/ethereum/go-ethereum/common" @@ -52,7 +52,7 @@ func PublishBlockHashes() { nonceB, err := subnetBInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) Expect(err).Should(BeNil()) - blockHashABI, err = teleporter_block_hash.TeleporterBlockHashMetaData.GetAbi() + blockHashABI, err = teleporter_block_hash.TeleporterblockhashreceiverMetaData.GetAbi() Expect(err).Should(BeNil()) blockHashReceiverAddressB, err = deploymentUtils.DeriveEVMContractAddress(fundedAddress, nonceB) Expect(err).Should(BeNil()) @@ -249,7 +249,7 @@ func PublishBlockHashes() { }) Expect(err).Should(BeNil()) - bind, err := teleporter_block_hash.NewTeleporterBlockHash(blockHashReceiverAddressB, subnetBInfo.ChainWSClient) + bind, err := teleporter_block_hash.NewTeleporterblockhashreceiver(blockHashReceiverAddressB, subnetBInfo.ChainWSClient) Expect(err).Should(BeNil()) event, err := bind.ParseReceiveBlockHash(logs[0]) Expect(err).Should(BeNil()) From 191fade8ac6d532d1f3d117056d461eced63edf0 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 20:05:13 +0000 Subject: [PATCH 19/26] enable basic relay test --- tests/e2e_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 397c207f..04b7c710 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -35,7 +35,7 @@ var _ = ginkgo.BeforeSuite(setupSuite) var _ = ginkgo.AfterSuite(teleporterTestUtils.TearDownNetwork) var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { - // ginkgo.It("Basic Relay", BasicRelay) + ginkgo.It("Basic Relay", BasicRelay) ginkgo.It("Publish Block Hashes", PublishBlockHashes) }) From abb2e00e79165bc8252b6b545cbbb34896ea5c93 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 20:05:31 +0000 Subject: [PATCH 20/26] linter --- messages/block_hash_publisher/config.go | 4 +--- messages/block_hash_publisher/config_test.go | 7 +++---- messages/block_hash_publisher/message_manager.go | 6 +++--- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/messages/block_hash_publisher/config.go b/messages/block_hash_publisher/config.go index 8a097f20..fb813f6e 100644 --- a/messages/block_hash_publisher/config.go +++ b/messages/block_hash_publisher/config.go @@ -36,9 +36,7 @@ func (c *Config) Validate() error { if addr == "" { return errors.New("empty address in block hash publisher configuration") } - if strings.HasPrefix(addr, "0x") { - addr = addr[2:] - } + 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)) diff --git a/messages/block_hash_publisher/config_test.go b/messages/block_hash_publisher/config_test.go index f6548a9a..8a0e8eeb 100644 --- a/messages/block_hash_publisher/config_test.go +++ b/messages/block_hash_publisher/config_test.go @@ -3,15 +3,14 @@ package block_hash_publisher import ( "fmt" "testing" - "time" "github.com/stretchr/testify/require" ) type testResult struct { isTimeInterval bool - blockInterval int - timeIntervalSeconds time.Duration + blockInterval uint64 + timeIntervalSeconds uint64 } func TestConfigValidate(t *testing.T) { @@ -43,7 +42,7 @@ func TestConfigValidate(t *testing.T) { }, { isTimeInterval: true, - timeIntervalSeconds: 10 * time.Second, + timeIntervalSeconds: 10, }, }, }, diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index aa0f2ac1..59af1f73 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -39,7 +39,7 @@ func (d *destinationSenderInfo) shouldSend(blockTimestamp uint64, blockNumber ui } } else { interval := d.blockInterval - if blockNumber-d.lastBlock < uint64(interval) { + if blockNumber-d.lastBlock < interval { return false } } @@ -89,8 +89,8 @@ func NewMessageManager( } destinations[destinationID] = &destinationSenderInfo{ useTimeInterval: destination.useTimeInterval, - timeIntervalSeconds: uint64(destination.timeIntervalSeconds), - blockInterval: uint64(destination.blockInterval), + timeIntervalSeconds: destination.timeIntervalSeconds, + blockInterval: destination.blockInterval, address: common.HexToAddress(destination.Address), client: destinationClients[destinationID], } From 498396562017376e0fe061c43231b81707b1be2e Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 20:07:24 +0000 Subject: [PATCH 21/26] fix test --- vms/evm/contract_message_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vms/evm/contract_message_test.go b/vms/evm/contract_message_test.go index fbc50c91..f5e78f84 100644 --- a/vms/evm/contract_message_test.go +++ b/vms/evm/contract_message_test.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/awm-relayer/config" + "github.com/ava-labs/awm-relayer/vms/vmtypes" warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -76,14 +77,17 @@ func TestUnpack(t *testing.T) { t.Run(testCase.name, func(t *testing.T) { input, err := hex.DecodeString(testCase.input) require.NoError(t, err) + msgInfo := vmtypes.WarpMessageInfo{ + UnsignedMsgBytes: input, + } mockLogger.EXPECT().Error(gomock.Any(), gomock.Any()).Times(testCase.errorLogTimes) - msg, err := m.UnpackWarpMessage(input) + err = m.UnpackWarpMessage(&msgInfo) if testCase.expectError { require.Error(t, err) } else { require.NoError(t, err) - require.Equal(t, testCase.networkID, msg.WarpUnsignedMessage.NetworkID) + require.Equal(t, testCase.networkID, msgInfo.WarpUnsignedMessage.NetworkID) } }) } From 36c76a3bbc0c4f885576a324dd1c6804fc3d7d01 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 20:27:55 +0000 Subject: [PATCH 22/26] latest abi bindings --- go.mod | 2 +- go.sum | 2 ++ messages/block_hash_publisher/message_manager.go | 2 +- tests/basic_relay.go | 6 +++--- tests/publish_block_hashes.go | 6 +++--- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 9fa368b1..631a0e7c 100644 --- a/go.mod +++ b/go.mod @@ -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-20231005194159-a8e949bd37c9 + github.com/ava-labs/teleporter v0.0.0-20231005202233-cab06cef8e05 github.com/ethereum/go-ethereum v1.12.0 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 diff --git a/go.sum b/go.sum index 419775a7..25a2d9a0 100644 --- a/go.sum +++ b/go.sum @@ -73,6 +73,8 @@ github.com/ava-labs/teleporter v0.0.0-20231005141349-feb8fe1523b6 h1:0QOZ/xhAxbP 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/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= diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index 59af1f73..f662d869 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -10,7 +10,7 @@ import ( "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/vms" "github.com/ava-labs/awm-relayer/vms/vmtypes" - teleporter_block_hash "github.com/ava-labs/teleporter/abis/TeleporterBlockHashReceiver" + teleporter_block_hash "github.com/ava-labs/teleporter/abi-bindings/Teleporter/TeleporterBlockHashReceiver" "github.com/ethereum/go-ethereum/common" "go.uber.org/zap" ) diff --git a/tests/basic_relay.go b/tests/basic_relay.go index 58b76f59..441b364b 100644 --- a/tests/basic_relay.go +++ b/tests/basic_relay.go @@ -22,7 +22,7 @@ import ( predicateutils "github.com/ava-labs/subnet-evm/utils/predicate" warpPayload "github.com/ava-labs/subnet-evm/warp/payload" "github.com/ava-labs/subnet-evm/x/warp" - teleportermessenger "github.com/ava-labs/teleporter/abis/TeleporterMessenger" + teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/Teleporter/TeleporterMessenger" teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -180,7 +180,7 @@ func BasicRelay() { log.Info("Sending teleporter transaction", "destinationChainID", subnetBInfo.BlockchainID, "txHash", signedTx.Hash()) receipt := teleporterTestUtils.SendTransactionAndWaitForAcceptance(ctx, subnetAInfo.ChainWSClient, signedTx) - bind, err := teleportermessenger.NewTeleportermessenger(teleporterContractAddress, subnetAInfo.ChainWSClient) + bind, err := teleportermessenger.NewTeleporterMessenger(teleporterContractAddress, subnetAInfo.ChainWSClient) Expect(err).Should(BeNil()) sendEvent, err := teleporterTestUtils.GetSendEventFromLogs(receipt.Logs, bind) Expect(err).Should(BeNil()) @@ -222,7 +222,7 @@ func BasicRelay() { Expect(receipt.Status).Should(Equal(types.ReceiptStatusSuccessful)) // Check that the transaction emits ReceiveCrossChainMessage - bind, err = teleportermessenger.NewTeleportermessenger(teleporterContractAddress, subnetBInfo.ChainWSClient) + bind, err = teleportermessenger.NewTeleporterMessenger(teleporterContractAddress, subnetBInfo.ChainWSClient) Expect(err).Should(BeNil()) receiveEvent, err := teleporterTestUtils.GetReceiveEventFromLogs(receipt.Logs, bind) diff --git a/tests/publish_block_hashes.go b/tests/publish_block_hashes.go index faab73de..b7086192 100644 --- a/tests/publish_block_hashes.go +++ b/tests/publish_block_hashes.go @@ -19,7 +19,7 @@ import ( "github.com/ava-labs/subnet-evm/accounts/abi" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/interfaces" - teleporter_block_hash "github.com/ava-labs/teleporter/abis/TeleporterBlockHashReceiver" + teleporter_block_hash "github.com/ava-labs/teleporter/abi-bindings/Teleporter/TeleporterBlockHashReceiver" deploymentUtils "github.com/ava-labs/teleporter/contract-deployment/utils" teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils" "github.com/ethereum/go-ethereum/common" @@ -52,7 +52,7 @@ func PublishBlockHashes() { nonceB, err := subnetBInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) Expect(err).Should(BeNil()) - blockHashABI, err = teleporter_block_hash.TeleporterblockhashreceiverMetaData.GetAbi() + blockHashABI, err = teleporter_block_hash.TeleporterBlockHashReceiverMetaData.GetAbi() Expect(err).Should(BeNil()) blockHashReceiverAddressB, err = deploymentUtils.DeriveEVMContractAddress(fundedAddress, nonceB) Expect(err).Should(BeNil()) @@ -249,7 +249,7 @@ func PublishBlockHashes() { }) Expect(err).Should(BeNil()) - bind, err := teleporter_block_hash.NewTeleporterblockhashreceiver(blockHashReceiverAddressB, subnetBInfo.ChainWSClient) + bind, err := teleporter_block_hash.NewTeleporterBlockHashReceiver(blockHashReceiverAddressB, subnetBInfo.ChainWSClient) Expect(err).Should(BeNil()) event, err := bind.ParseReceiveBlockHash(logs[0]) Expect(err).Should(BeNil()) From 8bdddf0d0aebd6636a76d472c9cca25f986c54a7 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 21:21:09 +0000 Subject: [PATCH 23/26] remove stale comment --- messages/teleporter/message_manager.go | 1 - 1 file changed, 1 deletion(-) diff --git a/messages/teleporter/message_manager.go b/messages/teleporter/message_manager.go index 550befcd..0f8259e8 100644 --- a/messages/teleporter/message_manager.go +++ b/messages/teleporter/message_manager.go @@ -206,7 +206,6 @@ func (m *messageManager) messageDelivered( // SendMessage extracts the gasLimit and packs the call data to call the receiveCrossChainMessage method of the Teleporter contract, // and dispatches transaction construction and broadcast to the destination client -// TODO: Revisit the caching strategy so we can remove parsedVmPayload as a parameter func (m *messageManager) SendMessage(signedMessage *warp.Message, warpMessageInfo *vmtypes.WarpMessageInfo, destinationChainID ids.ID) error { teleporterMessage, ok := m.teleporterMessageCache.Get(signedMessage.ID()) if !ok { From 8d5cb1c58108c8526571870f5ddae181a39e6c48 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 5 Oct 2023 21:55:17 +0000 Subject: [PATCH 24/26] install forge in ci job --- .github/workflows/e2e.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 6221dd64..462e6c7d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -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 From c3d16394fbde5843a9b19b0d7c263e49d45e84d4 Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Fri, 13 Oct 2023 21:14:16 +0000 Subject: [PATCH 25/26] deliver block hash to multiple destinations --- go.mod | 2 +- go.sum | 2 + tests/publish_block_hashes.go | 195 ++++++++++++++++++++++------------ 3 files changed, 130 insertions(+), 69 deletions(-) diff --git a/go.mod b/go.mod index 631a0e7c..7828c8fc 100644 --- a/go.mod +++ b/go.mod @@ -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-20231005202233-cab06cef8e05 + github.com/ava-labs/teleporter v0.0.0-20231013203557-0810228e758d github.com/ethereum/go-ethereum v1.12.0 github.com/onsi/ginkgo/v2 v2.12.0 github.com/onsi/gomega v1.27.10 diff --git a/go.sum b/go.sum index 25a2d9a0..22130fe8 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,8 @@ github.com/ava-labs/teleporter v0.0.0-20231005194159-a8e949bd37c9 h1:Rwrhf+GXl9M 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/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= diff --git a/tests/publish_block_hashes.go b/tests/publish_block_hashes.go index b7086192..982bd218 100644 --- a/tests/publish_block_hashes.go +++ b/tests/publish_block_hashes.go @@ -3,12 +3,14 @@ package tests import ( "bufio" "context" + "crypto/ecdsa" "encoding/hex" "encoding/json" "fmt" "math/big" "os" "os/exec" + "sync" "time" "github.com/ava-labs/avalanchego/utils/logging" @@ -29,39 +31,25 @@ import ( . "github.com/onsi/gomega" ) -func PublishBlockHashes() { - var ( - relayerCmd *exec.Cmd - relayerCancel context.CancelFunc - blockHashReceiverAddressB common.Address - subnetAHashes []common.Hash - blockHashABI *abi.ABI - ) - - subnetAInfo := teleporterTestUtils.GetSubnetATestInfo() - subnetBInfo := teleporterTestUtils.GetSubnetBTestInfo() - fundedAddress, fundedKey := teleporterTestUtils.GetFundedAccountInfo() - - // - // Deploy block hash receiver on Subnet B - // - ctx := context.Background() - - blockHashReceiverByteCode := testUtils.ReadHexTextFile("./tests/utils/BlockHashReceiverByteCode.txt") - - nonceB, err := subnetBInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) +func deployBlockHashReceiver( + ctx context.Context, + subnetInfo teleporterTestUtils.SubnetTestInfo, + fundedAddress common.Address, + fundedKey *ecdsa.PrivateKey, + blockHashABI *abi.ABI, + blockHashReceiverByteCode string, +) common.Address { + nonce, err := subnetInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) Expect(err).Should(BeNil()) - blockHashABI, err = teleporter_block_hash.TeleporterBlockHashReceiverMetaData.GetAbi() - Expect(err).Should(BeNil()) - blockHashReceiverAddressB, err = deploymentUtils.DeriveEVMContractAddress(fundedAddress, nonceB) + blockHashReceiverAddress, err := deploymentUtils.DeriveEVMContractAddress(fundedAddress, nonce) Expect(err).Should(BeNil()) cmdOutput := make(chan string) cmd := exec.Command( "cast", "send", - "--rpc-url", teleporterTestUtils.HttpToRPCURI(subnetBInfo.ChainNodeURIs[0], subnetBInfo.BlockchainID.String()), + "--rpc-url", teleporterTestUtils.HttpToRPCURI(subnetInfo.ChainNodeURIs[0], subnetInfo.BlockchainID.String()), "--private-key", hexutil.Encode(fundedKey.D.Bytes()), "--create", blockHashReceiverByteCode, ) @@ -92,11 +80,81 @@ func PublishBlockHashes() { Expect(err).Should(BeNil()) // Confirm successful deployment - deployedCode, err := subnetBInfo.ChainWSClient.CodeAt(ctx, blockHashReceiverAddressB, nil) + deployedCode, err := subnetInfo.ChainWSClient.CodeAt(ctx, blockHashReceiverAddress, nil) Expect(err).Should(BeNil()) Expect(len(deployedCode)).Should(BeNumerically(">", 2)) // 0x is an EOA, contract returns the bytecode - log.Info("Deployed block hash receiver contract", "address", blockHashReceiverAddressB.Hex()) + log.Info("Deployed block hash receiver contract", "address", blockHashReceiverAddress.Hex()) + + return blockHashReceiverAddress +} + +func receiveBlockHash( + ctx context.Context, + blockHashReceiverAddress common.Address, + newHeads chan *types.Header, + subnetInfo teleporterTestUtils.SubnetTestInfo, + blockHashABI *abi.ABI, expectedHashes []common.Hash) { + newHead := <-newHeads + log.Info("Fetching log from the newly produced block") + + blockHashB := newHead.Hash() + + logs, err := subnetInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ + BlockHash: &blockHashB, + Addresses: []common.Address{blockHashReceiverAddress}, + Topics: [][]common.Hash{ + { + blockHashABI.Events["ReceiveBlockHash"].ID, + }, + }, + }) + Expect(err).Should(BeNil()) + + bind, err := teleporter_block_hash.NewTeleporterBlockHashReceiver(blockHashReceiverAddress, subnetInfo.ChainWSClient) + Expect(err).Should(BeNil()) + event, err := bind.ParseReceiveBlockHash(logs[0]) + Expect(err).Should(BeNil()) + + // The published block hash should match one of the ones sent on Subnet A + foundHash := false + for _, blockHash := range expectedHashes { + if hex.EncodeToString(blockHash[:]) == hex.EncodeToString(event.BlockHash[:]) { + foundHash = true + break + } + } + if !foundHash { + Expect(false).Should(BeTrue(), "published block hash does not match any of the sent block hashes") + } + log.Info( + "Received published block hash on destination", + "blockHash", hex.EncodeToString(event.BlockHash[:]), + "destinationChainID", subnetInfo.BlockchainID.String(), + ) + + // We shouldn't receive any more blocks, since the relayer is configured to publish once every 5 blocks on the source + log.Info("Waiting for 10s to ensure no new block confirmations on destination chain") + Consistently(newHeads, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive()) +} + +func PublishBlockHashes() { + subnetAInfo := teleporterTestUtils.GetSubnetATestInfo() + subnetBInfo := teleporterTestUtils.GetSubnetBTestInfo() + subnetCInfo := teleporterTestUtils.GetSubnetCTestInfo() + fundedAddress, fundedKey := teleporterTestUtils.GetFundedAccountInfo() + + // + // Deploy block hash receiver on Subnet B + // + ctx := context.Background() + + blockHashABI, err := teleporter_block_hash.TeleporterBlockHashReceiverMetaData.GetAbi() + Expect(err).Should(BeNil()) + blockHashReceiverByteCode := testUtils.ReadHexTextFile("./tests/utils/BlockHashReceiverByteCode.txt") + + blockHashReceiverAddressB := deployBlockHashReceiver(ctx, subnetBInfo, fundedAddress, fundedKey, blockHashABI, blockHashReceiverByteCode) + blockHashReceiverAddressC := deployBlockHashReceiver(ctx, subnetCInfo, fundedAddress, fundedKey, blockHashABI, blockHashReceiverByteCode) // // Setup relayer config @@ -107,16 +165,23 @@ func PublishBlockHashes() { hostB, portB, err := teleporterTestUtils.GetURIHostAndPort(subnetBInfo.ChainNodeURIs[0]) Expect(err).Should(BeNil()) + hostC, portC, err := teleporterTestUtils.GetURIHostAndPort(subnetCInfo.ChainNodeURIs[0]) + Expect(err).Should(BeNil()) + log.Info( "Setting up relayer config", "hostA", hostA, "portA", portA, "blockChainA", subnetAInfo.BlockchainID.String(), + "subnetA", subnetAInfo.SubnetID.String(), "hostB", hostB, "portB", portB, "blockChainB", subnetBInfo.BlockchainID.String(), - "subnetA", subnetAInfo.SubnetID.String(), "subnetB", subnetBInfo.SubnetID.String(), + "hostC", hostC, + "portC", portC, + "blockChainC", subnetCInfo.BlockchainID.String(), + "subnetC", subnetCInfo.SubnetID.String(), ) relayerConfig := config.Config{ @@ -147,6 +212,11 @@ func PublishBlockHashes() { Address: blockHashReceiverAddressB.String(), Interval: "5", }, + { + ChainID: subnetCInfo.BlockchainID.String(), + Address: blockHashReceiverAddressC.String(), + Interval: "5", + }, }, }, }, @@ -163,6 +233,15 @@ func PublishBlockHashes() { APINodePort: portB, AccountPrivateKey: hex.EncodeToString(fundedKey.D.Bytes()), }, + { + SubnetID: subnetCInfo.SubnetID.String(), + ChainID: subnetCInfo.BlockchainID.String(), + VM: config.EVM.String(), + EncryptConnection: false, + APINodeHost: hostC, + APINodePort: portC, + AccountPrivateKey: hex.EncodeToString(fundedKey.D.Bytes()), + }, }, } @@ -181,7 +260,7 @@ func PublishBlockHashes() { // // Build Relayer // - cmd = exec.Command("./scripts/build.sh") + cmd := exec.Command("./scripts/build.sh") out, err := cmd.CombinedOutput() fmt.Println(string(out)) Expect(err).Should(BeNil()) @@ -189,7 +268,7 @@ func PublishBlockHashes() { // // Publish block hashes // - relayerCmd, relayerCancel = testUtils.RunRelayerExecutable(ctx, relayerConfigPath) + relayerCmd, relayerCancel := testUtils.RunRelayerExecutable(ctx, relayerConfigPath) destinationAddress := common.HexToAddress("0x0000000000000000000000000000000000000000") gasTipCapA, err := subnetAInfo.ChainWSClient.SuggestGasTipCap(context.Background()) @@ -206,8 +285,14 @@ func PublishBlockHashes() { Expect(err).Should(BeNil()) defer subB.Unsubscribe() + newHeadsC := make(chan *types.Header, 10) + subC, err := subnetCInfo.ChainWSClient.SubscribeNewHead(ctx, newHeadsC) + Expect(err).Should(BeNil()) + defer subC.Unsubscribe() + // Send 5 transactions to produce 5 blocks on subnet A // We expect exactly one of the block hashes to be published by the relayer + subnetAHashes := []common.Hash{} for i := 0; i < 5; i++ { nonceA, err := subnetAInfo.ChainWSClient.NonceAt(ctx, fundedAddress, nil) Expect(err).Should(BeNil()) @@ -232,44 +317,18 @@ func PublishBlockHashes() { subnetAHashes = append(subnetAHashes, receipt.BlockHash) } - // Listen on the destination chain for the published block hash - newHeadB := <-newHeadsB - log.Info("Fetching log from the newly produced block") - - blockHashB := newHeadB.Hash() - - logs, err := subnetBInfo.ChainWSClient.FilterLogs(ctx, interfaces.FilterQuery{ - BlockHash: &blockHashB, - Addresses: []common.Address{blockHashReceiverAddressB}, - Topics: [][]common.Hash{ - { - blockHashABI.Events["ReceiveBlockHash"].ID, - }, - }, - }) - Expect(err).Should(BeNil()) - - bind, err := teleporter_block_hash.NewTeleporterBlockHashReceiver(blockHashReceiverAddressB, subnetBInfo.ChainWSClient) - Expect(err).Should(BeNil()) - event, err := bind.ParseReceiveBlockHash(logs[0]) - Expect(err).Should(BeNil()) - - // The published block hash should match one of the ones sent on Subnet A - foundHash := false - for _, blockHash := range subnetAHashes { - if hex.EncodeToString(blockHash[:]) == hex.EncodeToString(event.BlockHash[:]) { - foundHash = true - break - } - } - if !foundHash { - Expect(false).Should(BeTrue(), "published block hash does not match any of the sent block hashes") - } - log.Info("Received published block hash on destination", "blockHash", hex.EncodeToString(event.BlockHash[:])) - - // We shouldn't receive any more blocks, since the relayer is configured to publish once every 5 blocks on the source - log.Info("Waiting for 10s to ensure no new block confirmations on destination chain") - Consistently(newHeadsB, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive()) + // Listen on the destination chains for the published block hash + wg := sync.WaitGroup{} + wg.Add(2) + go func() { + defer wg.Done() + receiveBlockHash(ctx, blockHashReceiverAddressB, newHeadsB, subnetBInfo, blockHashABI, subnetAHashes) + }() + go func() { + defer wg.Done() + receiveBlockHash(ctx, blockHashReceiverAddressC, newHeadsC, subnetCInfo, blockHashABI, subnetAHashes) + }() + wg.Wait() // Cancel the command and stop the relayer relayerCancel() From e2095016bc550455a74b2e69deaeca907ea1fe8b Mon Sep 17 00:00:00 2001 From: cam-schultz Date: Thu, 19 Oct 2023 21:43:32 +0000 Subject: [PATCH 26/26] latest block hash receiver --- go.mod | 2 +- go.sum | 4 ++++ messages/block_hash_publisher/message_manager.go | 5 +---- tests/utils/BlockHashReceiverByteCode.txt | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 7828c8fc..8087dea7 100644 --- a/go.mod +++ b/go.mod @@ -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-20231013203557-0810228e758d + 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 diff --git a/go.sum b/go.sum index 22130fe8..9feeb939 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,10 @@ github.com/ava-labs/teleporter v0.0.0-20231005202233-cab06cef8e05 h1:qqTnNm4RHxH 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= diff --git a/messages/block_hash_publisher/message_manager.go b/messages/block_hash_publisher/message_manager.go index f662d869..1bf484fb 100644 --- a/messages/block_hash_publisher/message_manager.go +++ b/messages/block_hash_publisher/message_manager.go @@ -132,10 +132,7 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, warpMessageInf zap.String("warpMessageID", signedMessage.ID().String()), ) // Construct the transaction call data to call the receive cross chain message method of the receiver precompile. - callData, err := teleporter_block_hash.PackReceiveBlockHash(teleporter_block_hash.ReceiveBlockHashInput{ - MessageIndex: uint32(0), - SourceChainID: signedMessage.SourceChainID, - }) + callData, err := teleporter_block_hash.PackReceiveBlockHash(0) if err != nil { m.logger.Error( "Failed packing receiveCrossChainMessage call data", diff --git a/tests/utils/BlockHashReceiverByteCode.txt b/tests/utils/BlockHashReceiverByteCode.txt index efaf55db..6b222848 100644 --- a/tests/utils/BlockHashReceiverByteCode.txt +++ b/tests/utils/BlockHashReceiverByteCode.txt @@ -1 +1 @@ -0x608060405234801561001057600080fd5b50600160008190558055610280806100296000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806330b525591461003b578063b771b3bc14610050575b600080fd5b61004e61004936600461018f565b61007a565b005b61005e6005600160991b0181565b6040516001600160a01b03909116815260200160405180910390f35b600180541461009c5760405163a815ca6b60e01b815260040160405180910390fd5b600260015560405163ce7f592960e01b815263ffffffff8316600482015260009081906005600160991b019063ce7f592990602401606060405180830381865afa1580156100ee573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061011291906101c4565b9150915080610134576040516339ecf43d60e21b815260040160405180910390fd5b815183146101555760405163e23ef14d60e01b815260040160405180910390fd5b602082015182516040517f7770e5f72465e9b05c8076c3f2eac70898abe6a84f0259307d127c13e2a1a4e490600090a35050600180555050565b600080604083850312156101a257600080fd5b823563ffffffff811681146101b657600080fd5b946020939093013593505050565b60008082840360608112156101d857600080fd5b60408112156101e657600080fd5b506040516040810181811067ffffffffffffffff8211171561021857634e487b7160e01b600052604160045260246000fd5b60409081528451825260208086015190830152840151909250801515811461023f57600080fd5b80915050925092905056fea264697066735822122035befb02f079d9bed5ae8edf23154d8ee3b487f193f6962a513df22f76fd6b6464736f6c63430008120033 \ No newline at end of file +0x608060405234801561001057600080fd5b506001600081905580556102a3806100296000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063303bb4251461003b578063b771b3bc14610050575b600080fd5b61004e6100493660046101ba565b61007a565b005b61005e6005600160991b0181565b6040516001600160a01b03909116815260200160405180910390f35b600180541461009c5760405163a815ca6b60e01b815260040160405180910390fd5b600260015560405163ce7f592960e01b815263ffffffff8216600482015260009081906005600160991b019063ce7f592990602401606060405180830381865afa1580156100ee573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061011291906101e7565b91509150806101815760405162461bcd60e51b815260206004820152603160248201527f54656c65706f72746572426c6f636b4861736852656365697665723a20696e76604482015270616c69642077617270206d65737361676560781b606482015260840160405180910390fd5b602082015182516040517f7770e5f72465e9b05c8076c3f2eac70898abe6a84f0259307d127c13e2a1a4e490600090a350506001805550565b6000602082840312156101cc57600080fd5b813563ffffffff811681146101e057600080fd5b9392505050565b60008082840360608112156101fb57600080fd5b604081121561020957600080fd5b506040516040810181811067ffffffffffffffff8211171561023b57634e487b7160e01b600052604160045260246000fd5b60409081528451825260208086015190830152840151909250801515811461026257600080fd5b80915050925092905056fea2646970667358221220bcb05beb3e88ca671f681162bf1f075f28fed86a56b8ae3feae71b034d9d6eb564736f6c63430008120033 \ No newline at end of file