diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 95b568b4..de527337 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,6 +37,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: recursive # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 64890691..6a930fe3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -19,6 +19,8 @@ jobs: steps: - name: Checkout awm-relayer repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Set Go version run: | @@ -45,6 +47,8 @@ jobs: - name: Checkout awm-relayer repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Run E2E Tests run: AVALANCHEGO_BUILD_PATH=/tmp/e2e-test/avalanchego DATA_DIR=/tmp/e2e-test/data ./scripts/e2e_test.sh diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index a67bdb51..6a4e4fc4 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -19,6 +19,8 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Set Go version run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 55ffa03c..8fda2b29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,6 +17,7 @@ jobs: with: fetch-depth: 0 path: awm-relayer + submodules: recursive - name: Set Go version run: | diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index 24055925..409298da 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -18,6 +18,7 @@ jobs: uses: actions/checkout@v4 with: path: awm-relayer + submodules: recursive - name: Run Snyk uses: snyk/actions/golang@master diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 902ea5d4..070ea17c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,6 +19,8 @@ jobs: steps: - name: Checkout awm-relayer repository uses: actions/checkout@v4 + with: + submodules: recursive - name: Set Go version run: | diff --git a/.gitignore b/.gitignore index b6bef6d8..003022bb 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,7 @@ relayer-config.json main.log server.log *.test + +# Foundry outputs +cache/ +out/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..15d57c4e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "tests/contracts/lib/teleporter"] + path = tests/contracts/lib/teleporter + url = https://github.com/ava-labs/teleporter +[submodule "tests/contracts/lib/forge-std"] + path = tests/contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/.golangci.yml b/.golangci.yml index 8ea94988..3a7ec484 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -3,13 +3,13 @@ run: timeout: 3m tests: true - # skip auto-generated files. - skip-files: - - ".*mock.*" issues: # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. max-same-issues: 0 + # skip auto-generated files. + exclude-files: + - ".*mock.*" linters: disable-all: true diff --git a/README.md b/README.md index 9e17e714..c64403ea 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,10 @@ awm-relayer --version Display awm-relayer vers awm-relayer --help Display awm-relayer usage and exit. ``` +### Initialize the repository + +- Get all submodules: `git submodule update --init --recursive` + ### Building Before building, be sure to install Go, which is required even if you're just building the Docker image. diff --git a/database/relayer_id.go b/database/relayer_id.go index 50cc692f..e1c0d6b1 100644 --- a/database/relayer_id.go +++ b/database/relayer_id.go @@ -53,7 +53,7 @@ func CalculateRelayerID( sourceBlockchainID ids.ID, destinationBlockchainID ids.ID, originSenderAddress common.Address, - desinationAddress common.Address, + destinationAddress common.Address, ) common.Hash { return crypto.Keccak256Hash( []byte(strings.Join( @@ -61,7 +61,7 @@ func CalculateRelayerID( sourceBlockchainID.String(), destinationBlockchainID.String(), originSenderAddress.String(), - desinationAddress.String(), + destinationAddress.String(), }, "-", )), diff --git a/database/utils.go b/database/utils.go index 8c56d083..00d1f741 100644 --- a/database/utils.go +++ b/database/utils.go @@ -45,7 +45,7 @@ func CalculateStartingBlockHeight( } else if err != nil { // Otherwise, we've encountered an unknown database error logger.Error( - "failed to get latest block from database", + "Failed to get latest block from database", zap.String("relayerID", relayerID.ID.String()), zap.Error(err), ) diff --git a/main/main.go b/main/main.go index 3844fe90..58995590 100644 --- a/main/main.go +++ b/main/main.go @@ -19,11 +19,14 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/database" + "github.com/ava-labs/awm-relayer/ethclient" "github.com/ava-labs/awm-relayer/peers" "github.com/ava-labs/awm-relayer/relayer" + "github.com/ava-labs/awm-relayer/types" relayerTypes "github.com/ava-labs/awm-relayer/types" "github.com/ava-labs/awm-relayer/utils" "github.com/ava-labs/awm-relayer/vms" + "github.com/ethereum/go-ethereum/common" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "go.uber.org/atomic" @@ -122,7 +125,7 @@ func main() { if logLevel <= logging.Debug { networkLogLevel = logLevel } - network, responseChans, err := peers.NewNetwork( + network, err := peers.NewNetwork( networkLogLevel, registerer, &cfg, @@ -200,18 +203,27 @@ func main() { ticker := utils.NewTicker(cfg.DBWriteIntervalSeconds) go ticker.Run() - manualWarpMessages := make(map[ids.ID][]*relayerTypes.WarpLogInfo) + // Gather manual Warp messages specified in the configuration + manualWarpMessages := make(map[ids.ID][]*relayerTypes.WarpMessageInfo) for _, msg := range cfg.ManualWarpMessages { sourceBlockchainID := msg.GetSourceBlockchainID() - - warpLogInfo := relayerTypes.WarpLogInfo{ - SourceAddress: msg.GetSourceAddress(), - UnsignedMsgBytes: msg.GetUnsignedMessageBytes(), + unsignedMsg, err := types.UnpackWarpMessage(msg.GetUnsignedMessageBytes()) + if err != nil { + logger.Error( + "Failed to unpack manual Warp message", + zap.String("warpMessageBytes", hex.EncodeToString(msg.GetUnsignedMessageBytes())), + zap.Error(err), + ) + panic(err) + } + warpLogInfo := relayerTypes.WarpMessageInfo{ + SourceAddress: msg.GetSourceAddress(), + UnsignedMessage: unsignedMsg, } manualWarpMessages[sourceBlockchainID] = append(manualWarpMessages[sourceBlockchainID], &warpLogInfo) } - // Create relayers for each of the subnets configured as a source + // Create listeners for each of the subnets configured as a source errGroup, ctx := errgroup.WithContext(context.Background()) for _, s := range cfg.SourceBlockchains { blockchainID, err := ids.FromString(s.BlockchainID) @@ -222,28 +234,67 @@ func main() { ) panic(err) } - subnetInfo := s + sourceBlockchain := s health := atomic.NewBool(true) relayerHealth[blockchainID] = health // errgroup will cancel the context when the first goroutine returns an error errGroup.Go(func() error { - // runRelayer runs until it errors or the context is cancelled by another goroutine - return runRelayer( + // Dial the eth client + ethClient, err := ethclient.DialWithConfig( + context.Background(), + sourceBlockchain.RPCEndpoint.BaseURL, + sourceBlockchain.RPCEndpoint.HTTPHeaders, + sourceBlockchain.RPCEndpoint.QueryParams, + ) + if err != nil { + logger.Error( + "Failed to connect to node via RPC", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.Error(err), + ) + return err + } + + // Create the ApplicationRelayers + applicationRelayers, minHeight, err := createApplicationRelayers( ctx, logger, metrics, db, ticker, - *subnetInfo, + *sourceBlockchain, network, - responseChans[blockchainID], - destinationClients, messageCreator, + &cfg, + ethClient, + destinationClients, + ) + if err != nil { + logger.Error( + "Failed to create application relayers", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.Error(err), + ) + return err + } + logger.Info( + "Created application relayers", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + ) + + // runListener runs until it errors or the context is cancelled by another goroutine + return runListener( + ctx, + logger, + *sourceBlockchain, health, manualWarpMessages[blockchainID], &cfg, + ethClient, + applicationRelayers, + minHeight, ) }) } @@ -254,69 +305,48 @@ func main() { ) } -// runRelayer creates a relayer instance for a subnet. It listens for warp messages on that subnet, and handles delivery to the destination -func runRelayer( +// runListener creates a Listener instance and the ApplicationRelayers for a subnet. +// The Listener listens for warp messages on that subnet, and the ApplicationRelayers handle delivery to the destination +func runListener( ctx context.Context, logger logging.Logger, - metrics *relayer.ApplicationRelayerMetrics, - db database.RelayerDatabase, - ticker *utils.Ticker, - sourceSubnetInfo config.SourceBlockchain, - network *peers.AppRequestNetwork, - responseChan chan message.InboundMessage, - destinationClients map[ids.ID]vms.DestinationClient, - messageCreator message.Creator, + sourceBlockchain config.SourceBlockchain, relayerHealth *atomic.Bool, - manualWarpMessages []*relayerTypes.WarpLogInfo, - cfg *config.Config, + manualWarpMessages []*relayerTypes.WarpMessageInfo, + globalConfig *config.Config, + ethClient ethclient.Client, + applicationRelayers map[common.Hash]*relayer.ApplicationRelayer, + minHeight uint64, ) error { - logger.Info( - "Creating relayer", - zap.String("originBlockchainID", sourceSubnetInfo.BlockchainID), - ) - + // Create the Listener listener, err := relayer.NewListener( logger, - metrics, - db, - ticker, - sourceSubnetInfo, - network, - responseChan, - destinationClients, - messageCreator, + sourceBlockchain, relayerHealth, - cfg, + globalConfig, + applicationRelayers, + minHeight, + ethClient, ) if err != nil { return fmt.Errorf("failed to create listener instance: %w", err) } logger.Info( "Created listener", - zap.String("blockchainID", sourceSubnetInfo.BlockchainID), + zap.String("blockchainID", sourceBlockchain.BlockchainID), ) - - // Send any messages that were specified in the configuration - for _, warpMessage := range manualWarpMessages { - logger.Info( - "Relaying manual Warp message", - zap.String("blockchainID", sourceSubnetInfo.BlockchainID), - zap.String("warpMessageBytes", hex.EncodeToString(warpMessage.UnsignedMsgBytes)), + err = listener.ProcessManualWarpMessages(logger, manualWarpMessages, sourceBlockchain) + if err != nil { + logger.Error( + "Failed to process manual Warp messages", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.Error(err), ) - err := listener.RouteManualWarpMessage(warpMessage) - if err != nil { - logger.Error( - "Failed to relay manual Warp message. Continuing.", - zap.Error(err), - zap.String("warpMessageBytes", hex.EncodeToString(warpMessage.UnsignedMsgBytes)), - ) - continue - } } logger.Info( "Listener initialized. Listening for messages to relay.", - zap.String("originBlockchainID", sourceSubnetInfo.BlockchainID), + zap.String("originBlockchainID", sourceBlockchain.BlockchainID), ) // Wait for logs from the subscribed node @@ -324,6 +354,84 @@ func runRelayer( return listener.ProcessLogs(ctx) } +// createApplicationRelayers creates Application Relayers for a given source blockchain. +func createApplicationRelayers( + ctx context.Context, + logger logging.Logger, + metrics *relayer.ApplicationRelayerMetrics, + db database.RelayerDatabase, + ticker *utils.Ticker, + sourceBlockchain config.SourceBlockchain, + network *peers.AppRequestNetwork, + messageCreator message.Creator, + cfg *config.Config, + srcEthClient ethclient.Client, + destinationClients map[ids.ID]vms.DestinationClient, +) (map[common.Hash]*relayer.ApplicationRelayer, uint64, error) { + // Create the ApplicationRelayers + logger.Info( + "Creating application relayers", + zap.String("originBlockchainID", sourceBlockchain.BlockchainID), + ) + applicationRelayers := make(map[common.Hash]*relayer.ApplicationRelayer) + + currentHeight, err := srcEthClient.BlockNumber(context.Background()) + if err != nil { + logger.Error( + "Failed to get current block height", + zap.Error(err), + ) + return nil, 0, err + } + + // Each ApplicationRelayer determines its starting height based on the database state. + // The Listener begins processing messages starting from the minimum height across all of the ApplicationRelayers + minHeight := uint64(0) + for _, relayerID := range database.GetSourceBlockchainRelayerIDs(&sourceBlockchain) { + height, err := database.CalculateStartingBlockHeight( + logger, + db, + relayerID, + sourceBlockchain.ProcessHistoricalBlocksFromHeight, + currentHeight, + ) + if err != nil { + logger.Error( + "Failed to calculate starting block height", + zap.String("relayerID", relayerID.ID.String()), + zap.Error(err), + ) + return nil, 0, err + } + if minHeight == 0 || height < minHeight { + minHeight = height + } + applicationRelayer, err := relayer.NewApplicationRelayer( + logger, + metrics, + network, + messageCreator, + relayerID, + db, + ticker, + destinationClients[relayerID.DestinationBlockchainID], + sourceBlockchain, + height, + cfg, + ) + if err != nil { + logger.Error( + "Failed to create application relayer", + zap.String("relayerID", relayerID.ID.String()), + zap.Error(err), + ) + return nil, 0, err + } + applicationRelayers[relayerID.ID] = applicationRelayer + } + return applicationRelayers, minHeight, nil +} + func startMetricsServer(logger logging.Logger, gatherer prometheus.Gatherer, port uint16) { http.Handle("/metrics", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{})) diff --git a/messages/message_handler.go b/messages/message_handler.go new file mode 100644 index 00000000..1ebfc04c --- /dev/null +++ b/messages/message_handler.go @@ -0,0 +1,43 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +//go:generate mockgen -source=$GOFILE -destination=./mocks/mock_message_handler.go -package=mocks + +package messages + +import ( + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/awm-relayer/vms" + "github.com/ethereum/go-ethereum/common" +) + +// MessageManager is specific to each message protocol. The interface handles choosing which messages to send +// for each message protocol, and performs the sending to the destination chain. +type MessageHandlerFactory interface { + // Create a message handler to relay the Warp message + NewMessageHandler(unsignedMessage *warp.UnsignedMessage) (MessageHandler, error) +} + +// MessageHandlers relay a single Warp message. A new instance should be created for each Warp message. +type MessageHandler interface { + // ShouldSendMessage returns true if the message should be sent to the destination chain + // If an error is returned, the boolean should be ignored by the caller. + ShouldSendMessage(destinationClient vms.DestinationClient) (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, destinationClient vms.DestinationClient) error + + // GetMessageRoutingInfo returns the source chain ID, origin sender address, destination chain ID, and destination address + GetMessageRoutingInfo() ( + ids.ID, + common.Address, + ids.ID, + common.Address, + error, + ) + + // GetUnsignedMessage returns the unsigned message + GetUnsignedMessage() *warp.UnsignedMessage +} diff --git a/messages/message_manager.go b/messages/message_manager.go deleted file mode 100644 index 00902777..00000000 --- a/messages/message_manager.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -//go:generate mockgen -source=$GOFILE -destination=./mocks/mock_message_manager.go -package=mocks - -package messages - -import ( - "fmt" - - "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" - offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" - "github.com/ava-labs/awm-relayer/messages/teleporter" - "github.com/ava-labs/awm-relayer/vms" - "github.com/ethereum/go-ethereum/common" -) - -// MessageManager is specific to each message protocol. The interface handles choosing which messages to send -// for each message protocol, and performs the sending to the destination chain. -type MessageManager interface { - // ShouldSendMessage returns true if the message should be sent to the destination chain - // If an error is returned, the boolean should be ignored by the caller. - ShouldSendMessage(unsignedMessage *warp.UnsignedMessage, destinationBlockchainID 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, destinationBlockchainID ids.ID) error - - // GetMessageRoutingInfo returns the source chain ID, origin sender address, destination chain ID, and destination address - GetMessageRoutingInfo(unsignedMessage *warp.UnsignedMessage) ( - ids.ID, - common.Address, - ids.ID, - common.Address, - error, - ) -} - -// NewMessageManager constructs a MessageManager for a particular message protocol, defined by the message protocol address and config -// Note that DestinationClients may be invoked concurrently by many MessageManagers, so it is assumed that they are implemented in a thread-safe way -func NewMessageManager( - logger logging.Logger, - messageProtocolAddress common.Address, - messageProtocolConfig config.MessageProtocolConfig, - destinationClients map[ids.ID]vms.DestinationClient, -) (MessageManager, error) { - format := messageProtocolConfig.MessageFormat - switch config.ParseMessageProtocol(format) { - case config.TELEPORTER: - return teleporter.NewMessageManager( - logger, - messageProtocolAddress, - messageProtocolConfig, - destinationClients, - ) - case config.OFF_CHAIN_REGISTRY: - return offchainregistry.NewMessageManager( - logger, - messageProtocolConfig, - destinationClients, - ) - default: - return nil, fmt.Errorf("invalid message format %s", format) - } -} diff --git a/messages/mocks/mock_message_handler.go b/messages/mocks/mock_message_handler.go new file mode 100644 index 00000000..18c9b8e1 --- /dev/null +++ b/messages/mocks/mock_message_handler.go @@ -0,0 +1,142 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: message_handler.go +// +// Generated by this command: +// +// mockgen -source=message_handler.go -destination=./mocks/mock_message_handler.go -package=mocks +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + reflect "reflect" + + ids "github.com/ava-labs/avalanchego/ids" + warp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + messages "github.com/ava-labs/awm-relayer/messages" + common "github.com/ethereum/go-ethereum/common" + gomock "go.uber.org/mock/gomock" +) + +// MockMessageHandlerFactory is a mock of MessageHandlerFactory interface. +type MockMessageHandlerFactory struct { + ctrl *gomock.Controller + recorder *MockMessageHandlerFactoryMockRecorder +} + +// MockMessageHandlerFactoryMockRecorder is the mock recorder for MockMessageHandlerFactory. +type MockMessageHandlerFactoryMockRecorder struct { + mock *MockMessageHandlerFactory +} + +// NewMockMessageHandlerFactory creates a new mock instance. +func NewMockMessageHandlerFactory(ctrl *gomock.Controller) *MockMessageHandlerFactory { + mock := &MockMessageHandlerFactory{ctrl: ctrl} + mock.recorder = &MockMessageHandlerFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMessageHandlerFactory) EXPECT() *MockMessageHandlerFactoryMockRecorder { + return m.recorder +} + +// NewMessageHandler mocks base method. +func (m *MockMessageHandlerFactory) NewMessageHandler(unsignedMessage *warp.UnsignedMessage) (messages.MessageHandler, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NewMessageHandler", unsignedMessage) + ret0, _ := ret[0].(messages.MessageHandler) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NewMessageHandler indicates an expected call of NewMessageHandler. +func (mr *MockMessageHandlerFactoryMockRecorder) NewMessageHandler(unsignedMessage any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewMessageHandler", reflect.TypeOf((*MockMessageHandlerFactory)(nil).NewMessageHandler), unsignedMessage) +} + +// MockMessageHandler is a mock of MessageHandler interface. +type MockMessageHandler struct { + ctrl *gomock.Controller + recorder *MockMessageHandlerMockRecorder +} + +// MockMessageHandlerMockRecorder is the mock recorder for MockMessageHandler. +type MockMessageHandlerMockRecorder struct { + mock *MockMessageHandler +} + +// NewMockMessageHandler creates a new mock instance. +func NewMockMessageHandler(ctrl *gomock.Controller) *MockMessageHandler { + mock := &MockMessageHandler{ctrl: ctrl} + mock.recorder = &MockMessageHandlerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMessageHandler) EXPECT() *MockMessageHandlerMockRecorder { + return m.recorder +} + +// GetMessageRoutingInfo mocks base method. +func (m *MockMessageHandler) GetMessageRoutingInfo() (ids.ID, common.Address, ids.ID, common.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMessageRoutingInfo") + ret0, _ := ret[0].(ids.ID) + ret1, _ := ret[1].(common.Address) + ret2, _ := ret[2].(ids.ID) + ret3, _ := ret[3].(common.Address) + ret4, _ := ret[4].(error) + return ret0, ret1, ret2, ret3, ret4 +} + +// GetMessageRoutingInfo indicates an expected call of GetMessageRoutingInfo. +func (mr *MockMessageHandlerMockRecorder) GetMessageRoutingInfo() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMessageRoutingInfo", reflect.TypeOf((*MockMessageHandler)(nil).GetMessageRoutingInfo)) +} + +// GetUnsignedMessage mocks base method. +func (m *MockMessageHandler) GetUnsignedMessage() *warp.UnsignedMessage { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetUnsignedMessage") + ret0, _ := ret[0].(*warp.UnsignedMessage) + return ret0 +} + +// GetUnsignedMessage indicates an expected call of GetUnsignedMessage. +func (mr *MockMessageHandlerMockRecorder) GetUnsignedMessage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUnsignedMessage", reflect.TypeOf((*MockMessageHandler)(nil).GetUnsignedMessage)) +} + +// SendMessage mocks base method. +func (m *MockMessageHandler) SendMessage(signedMessage *warp.Message, destinationBlockchainID ids.ID) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SendMessage", signedMessage, destinationBlockchainID) + ret0, _ := ret[0].(error) + return ret0 +} + +// SendMessage indicates an expected call of SendMessage. +func (mr *MockMessageHandlerMockRecorder) SendMessage(signedMessage, destinationBlockchainID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockMessageHandler)(nil).SendMessage), signedMessage, destinationBlockchainID) +} + +// ShouldSendMessage mocks base method. +func (m *MockMessageHandler) ShouldSendMessage(destinationBlockchainID ids.ID) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ShouldSendMessage", destinationBlockchainID) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ShouldSendMessage indicates an expected call of ShouldSendMessage. +func (mr *MockMessageHandlerMockRecorder) ShouldSendMessage(destinationBlockchainID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldSendMessage", reflect.TypeOf((*MockMessageHandler)(nil).ShouldSendMessage), destinationBlockchainID) +} diff --git a/messages/mocks/mock_message_manager.go b/messages/mocks/mock_message_manager.go deleted file mode 100644 index 4e4bb4b4..00000000 --- a/messages/mocks/mock_message_manager.go +++ /dev/null @@ -1,84 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: message_manager.go -// -// Generated by this command: -// -// mockgen -source=message_manager.go -destination=./mocks/mock_message_manager.go -package=mocks -// -// Package mocks is a generated GoMock package. -package mocks - -import ( - reflect "reflect" - - ids "github.com/ava-labs/avalanchego/ids" - warp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - gomock "go.uber.org/mock/gomock" -) - -// MockMessageManager is a mock of MessageManager interface. -type MockMessageManager struct { - ctrl *gomock.Controller - recorder *MockMessageManagerMockRecorder -} - -// MockMessageManagerMockRecorder is the mock recorder for MockMessageManager. -type MockMessageManagerMockRecorder struct { - mock *MockMessageManager -} - -// NewMockMessageManager creates a new mock instance. -func NewMockMessageManager(ctrl *gomock.Controller) *MockMessageManager { - mock := &MockMessageManager{ctrl: ctrl} - mock.recorder = &MockMessageManagerMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockMessageManager) EXPECT() *MockMessageManagerMockRecorder { - return m.recorder -} - -// GetDestinationBlockchainID mocks base method. -func (m *MockMessageManager) GetDestinationBlockchainID(unsignedMessage *warp.UnsignedMessage) (ids.ID, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDestinationBlockchainID", unsignedMessage) - ret0, _ := ret[0].(ids.ID) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetDestinationBlockchainID indicates an expected call of GetDestinationBlockchainID. -func (mr *MockMessageManagerMockRecorder) GetDestinationBlockchainID(unsignedMessage any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDestinationBlockchainID", reflect.TypeOf((*MockMessageManager)(nil).GetDestinationBlockchainID), unsignedMessage) -} - -// SendMessage mocks base method. -func (m *MockMessageManager) SendMessage(signedMessage *warp.Message, destinationBlockchainID ids.ID) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendMessage", signedMessage, destinationBlockchainID) - ret0, _ := ret[0].(error) - return ret0 -} - -// SendMessage indicates an expected call of SendMessage. -func (mr *MockMessageManagerMockRecorder) SendMessage(signedMessage, destinationBlockchainID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendMessage", reflect.TypeOf((*MockMessageManager)(nil).SendMessage), signedMessage, destinationBlockchainID) -} - -// ShouldSendMessage mocks base method. -func (m *MockMessageManager) ShouldSendMessage(unsignedMessage *warp.UnsignedMessage, destinationBlockchainID ids.ID) (bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ShouldSendMessage", unsignedMessage, destinationBlockchainID) - ret0, _ := ret[0].(bool) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// ShouldSendMessage indicates an expected call of ShouldSendMessage. -func (mr *MockMessageManagerMockRecorder) ShouldSendMessage(unsignedMessage, destinationBlockchainID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ShouldSendMessage", reflect.TypeOf((*MockMessageManager)(nil).ShouldSendMessage), unsignedMessage, destinationBlockchainID) -} diff --git a/messages/off-chain-registry/message_manager.go b/messages/off-chain-registry/message_handler.go similarity index 69% rename from messages/off-chain-registry/message_manager.go rename to messages/off-chain-registry/message_handler.go index 889eabff..f6b0d548 100644 --- a/messages/off-chain-registry/message_manager.go +++ b/messages/off-chain-registry/message_handler.go @@ -14,6 +14,7 @@ import ( warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/ethclient" + "github.com/ava-labs/awm-relayer/messages" "github.com/ava-labs/awm-relayer/vms" "github.com/ava-labs/subnet-evm/accounts/abi/bind" teleporterregistry "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/upgrades/TeleporterRegistry" @@ -28,17 +29,21 @@ const ( revertVersionNotFoundString = "TeleporterRegistry: version not found" ) -type messageManager struct { - logger logging.Logger - destinationClients map[ids.ID]vms.DestinationClient - registryAddress common.Address +type factory struct { + logger logging.Logger + registryAddress common.Address } -func NewMessageManager( +type messageHandler struct { + logger logging.Logger + unsignedMessage *warp.UnsignedMessage + factory *factory +} + +func NewMessageHandlerFactory( logger logging.Logger, messageProtocolConfig config.MessageProtocolConfig, - destinationClients map[ids.ID]vms.DestinationClient, -) (*messageManager, error) { +) (messages.MessageHandlerFactory, error) { // Marshal the map and unmarshal into the off-chain registry config data, err := json.Marshal(messageProtocolConfig.Settings) if err != nil { @@ -58,17 +63,28 @@ func NewMessageManager( ) return nil, err } - return &messageManager{ - logger: logger, - destinationClients: destinationClients, - registryAddress: common.HexToAddress(messageConfig.TeleporterRegistryAddress), + return &factory{ + logger: logger, + registryAddress: common.HexToAddress(messageConfig.TeleporterRegistryAddress), }, nil } +func (f *factory) NewMessageHandler(unsignedMessage *warp.UnsignedMessage) (messages.MessageHandler, error) { + return &messageHandler{ + logger: f.logger, + unsignedMessage: unsignedMessage, + factory: f, + }, nil +} + +func (m *messageHandler) GetUnsignedMessage() *warp.UnsignedMessage { + return m.unsignedMessage +} + // ShouldSendMessage returns false if any contract is already registered as the specified version in the TeleporterRegistry contract. // This is because a single contract address can be registered to multiple versions, but each version may only map to a single contract address. -func (m *messageManager) ShouldSendMessage(unsignedMessage *warp.UnsignedMessage, destinationBlockchainID ids.ID) (bool, error) { - addressedPayload, err := warpPayload.ParseAddressedCall(unsignedMessage.Payload) +func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClient) (bool, error) { + addressedPayload, err := warpPayload.ParseAddressedCall(m.unsignedMessage.Payload) if err != nil { m.logger.Error( "Failed parsing addressed payload", @@ -84,27 +100,23 @@ func (m *messageManager) ShouldSendMessage(unsignedMessage *warp.UnsignedMessage ) return false, err } - if destination != m.registryAddress { + if destination != m.factory.registryAddress { m.logger.Info( "Message is not intended for the configured registry", zap.String("destination", destination.String()), - zap.String("configuredRegistry", m.registryAddress.String()), + zap.String("configuredRegistry", m.factory.registryAddress.String()), ) return false, nil } // Get the correct destination client from the global map - destinationClient, ok := m.destinationClients[destinationBlockchainID] - if !ok { - return false, fmt.Errorf("relayer not configured to deliver to destination. destinationBlockchainID=%s", destinationBlockchainID.String()) - } client, ok := destinationClient.Client().(ethclient.Client) if !ok { panic(fmt.Sprintf("Destination client for chain %s is not an Ethereum client", destinationClient.DestinationBlockchainID().String())) } // Check if the version is already registered in the TeleporterRegistry contract. - registry, err := teleporterregistry.NewTeleporterRegistryCaller(m.registryAddress, client) + registry, err := teleporterregistry.NewTeleporterRegistryCaller(m.factory.registryAddress, client) if err != nil { m.logger.Error( "Failed to create TeleporterRegistry caller", @@ -132,30 +144,24 @@ func (m *messageManager) ShouldSendMessage(unsignedMessage *warp.UnsignedMessage return false, nil } -func (m *messageManager) SendMessage(signedMessage *warp.Message, destinationBlockchainID ids.ID) error { - // Get the correct destination client from the global map - destinationClient, ok := m.destinationClients[destinationBlockchainID] - if !ok { - return fmt.Errorf("relayer not configured to deliver to destination. DestinationBlockchainID=%s", destinationBlockchainID) - } - +func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) error { // Construct the transaction call data to call the TeleporterRegistry contract. // Only one off-chain registry Warp message is sent at a time, so we hardcode the index to 0 in the call. callData, err := teleporterregistry.PackAddProtocolVersion(0) if err != nil { m.logger.Error( "Failed packing receiveCrossChainMessage call data", - zap.String("destinationBlockchainID", destinationBlockchainID.String()), + zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()), zap.String("warpMessageID", signedMessage.ID().String()), ) return err } - err = destinationClient.SendTx(signedMessage, m.registryAddress.Hex(), addProtocolVersionGasLimit, callData) + err = destinationClient.SendTx(signedMessage, m.factory.registryAddress.Hex(), addProtocolVersionGasLimit, callData) if err != nil { m.logger.Error( "Failed to send tx.", - zap.String("destinationBlockchainID", destinationBlockchainID.String()), + zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()), zap.String("warpMessageID", signedMessage.ID().String()), zap.Error(err), ) @@ -163,20 +169,20 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, destinationBlo } m.logger.Info( "Sent message to destination chain", - zap.String("destinationBlockchainID", destinationBlockchainID.String()), + zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()), zap.String("warpMessageID", signedMessage.ID().String()), ) return nil } -func (m *messageManager) GetMessageRoutingInfo(unsignedMessage *warp.UnsignedMessage) ( +func (m *messageHandler) GetMessageRoutingInfo() ( ids.ID, common.Address, ids.ID, common.Address, error, ) { - addressedPayload, err := warpPayload.ParseAddressedCall(unsignedMessage.Payload) + addressedPayload, err := warpPayload.ParseAddressedCall(m.unsignedMessage.Payload) if err != nil { m.logger.Error( "Failed parsing addressed payload", @@ -184,9 +190,9 @@ func (m *messageManager) GetMessageRoutingInfo(unsignedMessage *warp.UnsignedMes ) return ids.ID{}, common.Address{}, ids.ID{}, common.Address{}, err } - return unsignedMessage.SourceChainID, + return m.unsignedMessage.SourceChainID, common.BytesToAddress(addressedPayload.SourceAddress), - unsignedMessage.SourceChainID, - m.registryAddress, + m.unsignedMessage.SourceChainID, + m.factory.registryAddress, nil } diff --git a/messages/off-chain-registry/message_manager_test.go b/messages/off-chain-registry/message_handler_test.go similarity index 95% rename from messages/off-chain-registry/message_manager_test.go rename to messages/off-chain-registry/message_handler_test.go index c9c46768..a71b895f 100644 --- a/messages/off-chain-registry/message_manager_test.go +++ b/messages/off-chain-registry/message_handler_test.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/awm-relayer/config" - "github.com/ava-labs/awm-relayer/vms" mock_evm "github.com/ava-labs/awm-relayer/vms/evm/mocks" mock_vms "github.com/ava-labs/awm-relayer/vms/mocks" teleporterregistry "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/upgrades/TeleporterRegistry" @@ -129,14 +128,10 @@ func TestShouldSendMessage(t *testing.T) { logger := logging.NoLog{} mockClient := mock_vms.NewMockDestinationClient(ctrl) - destinationClients := map[ids.ID]vms.DestinationClient{ - test.destinationBlockchainID: mockClient, - } - messageManager, err := NewMessageManager( + factory, err := NewMessageHandlerFactory( logger, messageProtocolConfig, - destinationClients, ) require.NoError(t, err) ethClient := mock_evm.NewMockClient(ctrl) @@ -144,6 +139,7 @@ func TestShouldSendMessage(t *testing.T) { Client(). Return(ethClient). Times(test.clientTimes) + mockClient.EXPECT().DestinationBlockchainID().Return(test.destinationBlockchainID).AnyTimes() if test.getAddressFromVersionCall != nil { output, err := packGetAddressFromVersionOutput(test.getAddressFromVersionCall.expectedAddress) require.NoError(t, err) @@ -160,8 +156,9 @@ func TestShouldSendMessage(t *testing.T) { } else { unsignedMessage = createRegistryUnsignedWarpMessage(t, test.entry, teleporterRegistryAddress, test.destinationBlockchainID) } - - result, err := messageManager.ShouldSendMessage(unsignedMessage, test.destinationBlockchainID) + messageHandler, err := factory.NewMessageHandler(unsignedMessage) + require.NoError(t, err) + result, err := messageHandler.ShouldSendMessage(mockClient) if test.expectedError { require.Error(t, err) } else { diff --git a/messages/teleporter/message_manager.go b/messages/teleporter/message_handler.go similarity index 60% rename from messages/teleporter/message_manager.go rename to messages/teleporter/message_handler.go index 35c286e8..f0a4c3d9 100644 --- a/messages/teleporter/message_manager.go +++ b/messages/teleporter/message_handler.go @@ -7,13 +7,13 @@ import ( "encoding/json" "fmt" - "github.com/ava-labs/avalanchego/cache" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm/warp" warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/ethclient" + "github.com/ava-labs/awm-relayer/messages" "github.com/ava-labs/awm-relayer/vms" "github.com/ava-labs/subnet-evm/accounts/abi/bind" teleportermessenger "github.com/ava-labs/teleporter/abi-bindings/go/Teleporter/TeleporterMessenger" @@ -23,28 +23,24 @@ import ( "go.uber.org/zap" ) -const ( - teleporterMessageCacheSize = 100 -) - -type messageManager struct { +type factory struct { messageConfig Config protocolAddress common.Address + logger logging.Logger +} - // We parse teleporter messages in ShouldSendMessage, cache them to be reused in SendMessage - // The cache is keyed by the Warp message ID, NOT the Teleporter message ID - teleporterMessageCache *cache.LRU[ids.ID, *teleportermessenger.TeleporterMessage] - destinationClients map[ids.ID]vms.DestinationClient - - logger logging.Logger +type messageHandler struct { + logger logging.Logger + teleporterMessage *teleportermessenger.TeleporterMessage + unsignedMessage *warp.UnsignedMessage + factory *factory } -func NewMessageManager( +func NewMessageHandlerFactory( logger logging.Logger, messageProtocolAddress common.Address, messageProtocolConfig config.MessageProtocolConfig, - destinationClients map[ids.ID]vms.DestinationClient, -) (*messageManager, error) { +) (messages.MessageHandlerFactory, error) { // Marshal the map and unmarshal into the Teleporter config data, err := json.Marshal(messageProtocolConfig.Settings) if err != nil { @@ -64,14 +60,28 @@ func NewMessageManager( ) return nil, err } - teleporterMessageCache := &cache.LRU[ids.ID, *teleportermessenger.TeleporterMessage]{Size: teleporterMessageCacheSize} - return &messageManager{ - messageConfig: messageConfig, - protocolAddress: messageProtocolAddress, - teleporterMessageCache: teleporterMessageCache, - destinationClients: destinationClients, - logger: logger, + return &factory{ + messageConfig: messageConfig, + protocolAddress: messageProtocolAddress, + logger: logger, + }, nil +} + +func (f *factory) NewMessageHandler(unsignedMessage *warp.UnsignedMessage) (messages.MessageHandler, error) { + teleporterMessage, err := f.parseTeleporterMessage(unsignedMessage) + if err != nil { + f.logger.Error( + "Failed to parse teleporter message.", + zap.String("warpMessageID", unsignedMessage.ID().String()), + ) + return nil, err + } + return &messageHandler{ + logger: f.logger, + teleporterMessage: teleporterMessage, + unsignedMessage: unsignedMessage, + factory: f, }, nil } @@ -89,77 +99,56 @@ func isAllowedRelayer(allowedRelayers []common.Address, eoa common.Address) bool return false } -func (m *messageManager) GetMessageRoutingInfo(unsignedMessage *warp.UnsignedMessage) ( +func (m *messageHandler) GetUnsignedMessage() *warp.UnsignedMessage { + return m.unsignedMessage +} + +func (m *messageHandler) GetMessageRoutingInfo() ( ids.ID, common.Address, ids.ID, common.Address, error, ) { - teleporterMessage, err := m.parseTeleporterMessage(unsignedMessage) - if err != nil { - m.logger.Error( - "Failed to parse teleporter message.", - zap.String("warpMessageID", unsignedMessage.ID().String()), - ) - return ids.ID{}, common.Address{}, ids.ID{}, common.Address{}, err - } - return unsignedMessage.SourceChainID, - teleporterMessage.OriginSenderAddress, - teleporterMessage.DestinationBlockchainID, - teleporterMessage.DestinationAddress, + return m.unsignedMessage.SourceChainID, + m.teleporterMessage.OriginSenderAddress, + m.teleporterMessage.DestinationBlockchainID, + m.teleporterMessage.DestinationAddress, nil } // ShouldSendMessage returns true if the message should be sent to the destination chain -func (m *messageManager) ShouldSendMessage(unsignedMessage *warp.UnsignedMessage, destinationBlockchainID ids.ID) (bool, error) { - teleporterMessage, err := m.parseTeleporterMessage(unsignedMessage) - if err != nil { - m.logger.Error( - "Failed get teleporter message.", - zap.String("destinationBlockchainID", destinationBlockchainID.String()), - zap.String("warpMessageID", unsignedMessage.ID().String()), - zap.Error(err), - ) - return false, err - } - - // Get the correct destination client from the global map - destinationClient, ok := m.destinationClients[destinationBlockchainID] - if !ok { - // This shouldn't occur, since we already check this in Listener.RouteMessage. Return an error in this case. - return false, fmt.Errorf("relayer not configured to deliver to destination. destinationBlockchainID=%s", destinationBlockchainID.String()) - } - +func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClient) (bool, error) { + destinationBlockchainID := destinationClient.DestinationBlockchainID() teleporterMessageID, err := teleporterUtils.CalculateMessageID( - m.protocolAddress, - unsignedMessage.SourceChainID, + m.factory.protocolAddress, + m.unsignedMessage.SourceChainID, destinationBlockchainID, - teleporterMessage.MessageNonce, + m.teleporterMessage.MessageNonce, ) if err != nil { return false, fmt.Errorf("failed to calculate Teleporter message ID: %w", err) } senderAddress := destinationClient.SenderAddress() - if !isAllowedRelayer(teleporterMessage.AllowedRelayerAddresses, senderAddress) { + if !isAllowedRelayer(m.teleporterMessage.AllowedRelayerAddresses, senderAddress) { m.logger.Info( "Relayer EOA not allowed to deliver this message.", zap.String("destinationBlockchainID", destinationBlockchainID.String()), - zap.String("warpMessageID", unsignedMessage.ID().String()), + zap.String("warpMessageID", m.unsignedMessage.ID().String()), zap.String("teleporterMessageID", teleporterMessageID.String()), ) return false, nil } // Check if the message has already been delivered to the destination chain - teleporterMessenger := m.getTeleporterMessenger(destinationClient) + teleporterMessenger := m.factory.getTeleporterMessenger(destinationClient) delivered, err := teleporterMessenger.MessageReceived(&bind.CallOpts{}, teleporterMessageID) if err != nil { m.logger.Error( "Failed to check if message has been delivered to destination chain.", zap.String("destinationBlockchainID", destinationBlockchainID.String()), - zap.String("warpMessageID", unsignedMessage.ID().String()), + zap.String("warpMessageID", m.unsignedMessage.ID().String()), zap.String("teleporterMessageID", teleporterMessageID.String()), zap.Error(err), ) @@ -179,28 +168,13 @@ func (m *messageManager) ShouldSendMessage(unsignedMessage *warp.UnsignedMessage // 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, destinationBlockchainID ids.ID) error { - teleporterMessage, err := m.parseTeleporterMessage(&signedMessage.UnsignedMessage) - if err != nil { - m.logger.Error( - "Failed get teleporter message.", - zap.String("destinationBlockchainID", destinationBlockchainID.String()), - zap.String("warpMessageID", signedMessage.ID().String()), - ) - return err - } - - // Get the correct destination client from the global map - destinationClient, ok := m.destinationClients[destinationBlockchainID] - if !ok { - return fmt.Errorf("relayer not configured to deliver to destination. DestinationBlockchainID=%s", destinationBlockchainID) - } - +func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) error { + destinationBlockchainID := destinationClient.DestinationBlockchainID() teleporterMessageID, err := teleporterUtils.CalculateMessageID( - m.protocolAddress, + m.factory.protocolAddress, signedMessage.SourceChainID, destinationBlockchainID, - teleporterMessage.MessageNonce, + m.teleporterMessage.MessageNonce, ) if err != nil { return fmt.Errorf("failed to calculate Teleporter message ID: %w", err) @@ -222,12 +196,13 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, destinationBlo ) return err } + gasLimit, err := gasUtils.CalculateReceiveMessageGasLimit( numSigners, - teleporterMessage.RequiredGasLimit, + m.teleporterMessage.RequiredGasLimit, len(signedMessage.Bytes()), len(signedMessage.Payload), - len(teleporterMessage.Receipts), + len(m.teleporterMessage.Receipts), ) if err != nil { m.logger.Error( @@ -239,7 +214,7 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, destinationBlo return err } // Construct the transaction call data to call the receive cross chain message method of the receiver precompile. - callData, err := teleportermessenger.PackReceiveCrossChainMessage(0, common.HexToAddress(m.messageConfig.RewardAddress)) + callData, err := teleportermessenger.PackReceiveCrossChainMessage(0, common.HexToAddress(m.factory.messageConfig.RewardAddress)) if err != nil { m.logger.Error( "Failed packing receiveCrossChainMessage call data", @@ -250,7 +225,7 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, destinationBlo return err } - err = destinationClient.SendTx(signedMessage, m.protocolAddress.Hex(), gasLimit, callData) + err = destinationClient.SendTx(signedMessage, m.factory.protocolAddress.Hex(), gasLimit, callData) if err != nil { m.logger.Error( "Failed to send tx.", @@ -272,49 +247,40 @@ func (m *messageManager) SendMessage(signedMessage *warp.Message, destinationBlo // parseTeleporterMessage returns the Warp message's corresponding Teleporter message from the cache if it exists. // Otherwise parses the Warp message payload. -func (m *messageManager) parseTeleporterMessage(unsignedMessage *warp.UnsignedMessage) (*teleportermessenger.TeleporterMessage, error) { - // Check if the message has already been parsed - teleporterMessage, ok := m.teleporterMessageCache.Get(unsignedMessage.ID()) - if !ok { - // If not, parse the message - m.logger.Debug( - "Teleporter message to send not in cache. Extracting from signed warp message.", +func (f *factory) parseTeleporterMessage(unsignedMessage *warp.UnsignedMessage) (*teleportermessenger.TeleporterMessage, error) { + addressedPayload, err := warpPayload.ParseAddressedCall(unsignedMessage.Payload) + if err != nil { + f.logger.Error( + "Failed parsing addressed payload", + zap.Error(err), + ) + return nil, err + } + teleporterMessage, err := teleportermessenger.UnpackTeleporterMessage(addressedPayload.Payload) + if err != nil { + f.logger.Error( + "Failed unpacking teleporter message.", zap.String("warpMessageID", unsignedMessage.ID().String()), ) - addressedPayload, err := warpPayload.ParseAddressedCall(unsignedMessage.Payload) - if err != nil { - m.logger.Error( - "Failed parsing addressed payload", - zap.Error(err), - ) - return nil, err - } - teleporterMessage, err = teleportermessenger.UnpackTeleporterMessage(addressedPayload.Payload) - if err != nil { - m.logger.Error( - "Failed unpacking teleporter message.", - zap.String("warpMessageID", unsignedMessage.ID().String()), - ) - return nil, err - } - m.teleporterMessageCache.Put(unsignedMessage.ID(), teleporterMessage) + return nil, err } + return teleporterMessage, nil } // getTeleporterMessenger returns the Teleporter messenger instance for the destination chain. // Panic instead of returning errors because this should never happen, and if it does, we do not // want to log and swallow the error, since operations after this will fail too. -func (m *messageManager) getTeleporterMessenger(destinationClient vms.DestinationClient) *teleportermessenger.TeleporterMessenger { +func (f *factory) getTeleporterMessenger(destinationClient vms.DestinationClient) *teleportermessenger.TeleporterMessenger { client, ok := destinationClient.Client().(ethclient.Client) if !ok { panic(fmt.Sprintf("Destination client for chain %s is not an Ethereum client", destinationClient.DestinationBlockchainID().String())) } // Get the teleporter messenger contract - teleporterMessenger, err := teleportermessenger.NewTeleporterMessenger(m.protocolAddress, client) + teleporterMessenger, err := teleportermessenger.NewTeleporterMessenger(f.protocolAddress, client) if err != nil { - panic("Failed to get teleporter messenger contract") + panic(fmt.Sprintf("Failed to get teleporter messenger contract: %s", err.Error())) } return teleporterMessenger } diff --git a/messages/teleporter/message_manager_test.go b/messages/teleporter/message_handler_test.go similarity index 92% rename from messages/teleporter/message_manager_test.go rename to messages/teleporter/message_handler_test.go index 8c95b4bc..00557b49 100644 --- a/messages/teleporter/message_manager_test.go +++ b/messages/teleporter/message_handler_test.go @@ -12,7 +12,6 @@ import ( "github.com/ava-labs/avalanchego/vms/platformvm/warp" warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/awm-relayer/config" - "github.com/ava-labs/awm-relayer/vms" mock_evm "github.com/ava-labs/awm-relayer/vms/evm/mocks" mock_vms "github.com/ava-labs/awm-relayer/vms/mocks" "github.com/ava-labs/subnet-evm/accounts/abi/bind" @@ -106,7 +105,7 @@ func TestShouldSendMessage(t *testing.T) { senderAddressTimes int clientTimes int messageReceivedCall *CallContractChecker - expectedError bool + expectedParseError bool expectedResult bool }{ { @@ -127,7 +126,7 @@ func TestShouldSendMessage(t *testing.T) { name: "invalid message", destinationBlockchainID: destinationBlockchainID, warpUnsignedMessage: invalidWarpUnsignedMessage, - expectedError: true, + expectedParseError: true, }, { name: "invalid destination chain id", @@ -166,17 +165,21 @@ func TestShouldSendMessage(t *testing.T) { logger := logging.NoLog{} mockClient := mock_vms.NewMockDestinationClient(ctrl) - destinationClients := map[ids.ID]vms.DestinationClient{ - test.destinationBlockchainID: mockClient, - } - messageManager, err := NewMessageManager( + factory, err := NewMessageHandlerFactory( logger, messageProtocolAddress, messageProtocolConfig, - destinationClients, ) require.NoError(t, err) + messageHandler, err := factory.NewMessageHandler(test.warpUnsignedMessage) + if test.expectedParseError { + // If we expect an error parsing the Warp message, we should not call ShouldSendMessage + require.Error(t, err) + return + } else { + require.NoError(t, err) + } ethClient := mock_evm.NewMockClient(ctrl) mockClient.EXPECT(). Client(). @@ -186,6 +189,7 @@ func TestShouldSendMessage(t *testing.T) { SenderAddress(). Return(test.senderAddressResult). Times(test.senderAddressTimes) + mockClient.EXPECT().DestinationBlockchainID().Return(destinationBlockchainID).AnyTimes() if test.messageReceivedCall != nil { messageReceivedInput := interfaces.CallMsg{ From: bind.CallOpts{}.From, @@ -198,13 +202,9 @@ func TestShouldSendMessage(t *testing.T) { Times(test.messageReceivedCall.times) } - result, err := messageManager.ShouldSendMessage(test.warpUnsignedMessage, test.destinationBlockchainID) - if test.expectedError { - require.Error(t, err) - } else { - require.NoError(t, err) - require.Equal(t, test.expectedResult, result) - } + result, err := messageHandler.ShouldSendMessage(mockClient) + require.NoError(t, err) + require.Equal(t, test.expectedResult, result) }) } } diff --git a/peers/app_request_network.go b/peers/app_request_network.go index 8fe1b803..8a915cde 100644 --- a/peers/app_request_network.go +++ b/peers/app_request_network.go @@ -11,7 +11,6 @@ import ( "time" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/network" snowVdrs "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/constants" @@ -40,12 +39,12 @@ type AppRequestNetwork struct { validatorClient *validators.CanonicalValidatorClient } -// NewNetwork connects to a peers at the app request level. +// NewNetwork creates a p2p network client for interacting with validators func NewNetwork( logLevel logging.Level, registerer prometheus.Registerer, cfg *config.Config, -) (*AppRequestNetwork, map[ids.ID]chan message.InboundMessage, error) { +) (*AppRequestNetwork, error) { logger := logging.NewLogger( "awm-relayer-p2p", logging.NewWrappedCore( @@ -55,27 +54,14 @@ func NewNetwork( ), ) - // Create the test network for AppRequests - var trackedSubnets set.Set[ids.ID] - for _, sourceBlockchain := range cfg.SourceBlockchains { - trackedSubnets.Add(sourceBlockchain.GetSubnetID()) - } - - // Construct a response chan for each chain. Inbound messages will be routed to the proper channel in the handler - responseChans := make(map[ids.ID]chan message.InboundMessage) - for _, sourceBlockchain := range cfg.SourceBlockchains { - responseChan := make(chan message.InboundMessage, InboundMessageChannelSize) - responseChans[sourceBlockchain.GetBlockchainID()] = responseChan - } - responseChansLock := new(sync.RWMutex) - - handler, err := NewRelayerExternalHandler(logger, registerer, responseChans, responseChansLock) + // Create the handler for handling inbound app responses + handler, err := NewRelayerExternalHandler(logger, registerer) if err != nil { logger.Error( "Failed to create p2p network handler", zap.Error(err), ) - return nil, nil, err + return nil, err } infoAPI, err := NewInfoAPI(cfg.InfoAPI) @@ -84,7 +70,7 @@ func NewNetwork( "Failed to create info API", zap.Error(err), ) - return nil, nil, err + return nil, err } networkID, err := infoAPI.GetNetworkID(context.Background()) if err != nil { @@ -92,7 +78,12 @@ func NewNetwork( "Failed to get network ID", zap.Error(err), ) - return nil, nil, err + return nil, err + } + + var trackedSubnets set.Set[ids.ID] + for _, sourceBlockchain := range cfg.SourceBlockchains { + trackedSubnets.Add(sourceBlockchain.GetSubnetID()) } testNetwork, err := network.NewTestNetwork(logger, networkID, snowVdrs.NewManager(), trackedSubnets, handler) @@ -101,7 +92,7 @@ func NewNetwork( "Failed to create test network", zap.Error(err), ) - return nil, nil, err + return nil, err } validatorClient := validators.NewCanonicalValidatorClient(logger, cfg.PChainAPI) @@ -122,11 +113,11 @@ func NewNetwork( for _, sourceBlockchain := range cfg.SourceBlockchains { if sourceBlockchain.GetSubnetID() == constants.PrimaryNetworkID { if err := arNetwork.connectToPrimaryNetworkPeers(cfg, sourceBlockchain); err != nil { - return nil, nil, err + return nil, err } } else { if err := arNetwork.connectToNonPrimaryNetworkPeers(cfg, sourceBlockchain); err != nil { - return nil, nil, err + return nil, err } } } @@ -135,7 +126,7 @@ func NewNetwork( testNetwork.Dispatch() }) - return arNetwork, responseChans, nil + return arNetwork, nil } // ConnectPeers connects the network to peers with the given nodeIDs. diff --git a/peers/external_handler.go b/peers/external_handler.go index a5c1a1b2..60e14a1c 100644 --- a/peers/external_handler.go +++ b/peers/external_handler.go @@ -9,7 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" - "github.com/ava-labs/avalanchego/snow/engine/common" + avalancheCommon "github.com/ava-labs/avalanchego/snow/engine/common" "github.com/ava-labs/avalanchego/snow/networking/router" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/logging" @@ -25,18 +25,22 @@ var _ router.ExternalHandler = &RelayerExternalHandler{} // is possible for multiple concurrent calls to happen with different NodeIDs. // However, a given NodeID will only be performing one call at a time. type RelayerExternalHandler struct { - log logging.Logger - responseChansLock *sync.RWMutex - responseChans map[ids.ID]chan message.InboundMessage - timeoutManager timer.AdaptiveTimeoutManager + log logging.Logger + lock *sync.Mutex + responseChans map[uint32]chan message.InboundMessage + responsesCount map[uint32]expectedResponses + timeoutManager timer.AdaptiveTimeoutManager +} + +// expectedResponses counts the number of responses and compares against the expected number of responses +type expectedResponses struct { + expected, received int } // Create a new RelayerExternalHandler to forward relevant inbound app messages to the respective Teleporter application relayer, as well as handle timeouts. func NewRelayerExternalHandler( logger logging.Logger, registerer prometheus.Registerer, - responseChans map[ids.ID]chan message.InboundMessage, - responseChansLock *sync.RWMutex, ) (*RelayerExternalHandler, error) { // TODO: Leaving this static for now, but we may want to have this as a config option cfg := timer.AdaptiveTimeoutConfig{ @@ -59,10 +63,11 @@ func NewRelayerExternalHandler( go timeoutManager.Dispatch() return &RelayerExternalHandler{ - log: logger, - responseChansLock: responseChansLock, - responseChans: responseChans, - timeoutManager: timeoutManager, + log: logger, + lock: &sync.Mutex{}, + responseChans: make(map[uint32]chan message.InboundMessage), + responsesCount: make(map[uint32]expectedResponses), + timeoutManager: timeoutManager, }, nil } @@ -76,55 +81,12 @@ func NewRelayerExternalHandler( // When a cross-chain message is picked up by a Relayer, HandleInbound routes AppResponses traffic to the appropriate Relayer func (h *RelayerExternalHandler) HandleInbound(_ context.Context, inboundMessage message.InboundMessage) { h.log.Debug( - "Receiving message", + "Handling app response", zap.Stringer("op", inboundMessage.Op()), + zap.Stringer("from", inboundMessage.NodeID()), ) if inboundMessage.Op() == message.AppResponseOp || inboundMessage.Op() == message.AppErrorOp { - h.log.Info("Handling app response", zap.Stringer("from", inboundMessage.NodeID())) - - // Extract the message fields - m := inboundMessage.Message() - - // Get the blockchainID from the message. - // Note: we should NOT call GetSourceBlockchainID; this is for cross-chain messages using the vm2 interface - // For normal app requests messages, the calls result in the same value, but if the relayer handles an - // inbound cross-chain app message, then we would get the incorrect chain ID. - blockchainID, err := message.GetChainID(m) - if err != nil { - h.log.Error("Could not get blockchainID from message") - inboundMessage.OnFinishedHandling() - return - } - sourceBlockchainID, err := message.GetSourceChainID(m) - if err != nil { - h.log.Error("Could not get sourceBlockchainID from message") - inboundMessage.OnFinishedHandling() - return - } - requestID, ok := message.GetRequestID(m) - if !ok { - h.log.Error("Could not get requestID from message") - inboundMessage.OnFinishedHandling() - return - } - - reqID := ids.RequestID{ - NodeID: inboundMessage.NodeID(), - SourceChainID: sourceBlockchainID, - DestinationChainID: blockchainID, - RequestID: requestID, - Op: byte(inboundMessage.Op()), - } - h.RegisterResponse(reqID) - - // Route to the appropriate response channel. Do not block on this call, otherwise incoming message handling may be blocked - // OnFinishedHandling is called by the consumer of the response channel - go func(message.InboundMessage, ids.ID) { - h.responseChansLock.RLock() - defer h.responseChansLock.RUnlock() - - h.responseChans[blockchainID] <- inboundMessage - }(inboundMessage, blockchainID) + h.registerAppResponse(inboundMessage) } else { h.log.Debug("Ignoring message", zap.Stringer("op", inboundMessage.Op())) inboundMessage.OnFinishedHandling() @@ -147,16 +109,33 @@ func (h *RelayerExternalHandler) Disconnected(nodeID ids.NodeID) { ) } +// RegisterRequestID registers an AppRequest by requestID, and marks the number of expected responses, equivalent to the number of nodes requested. +// requestID should be globally unique for the lifetime of the AppRequest. This is upper bounded by the timeout duration. +func (h *RelayerExternalHandler) RegisterRequestID(requestID uint32, numExpectedResponses int) chan message.InboundMessage { + // Create a channel to receive the response + h.lock.Lock() + defer h.lock.Unlock() + + h.log.Debug("Registering request ID", zap.Uint32("requestID", requestID)) + + responseChan := make(chan message.InboundMessage, numExpectedResponses) + h.responseChans[requestID] = responseChan + h.responsesCount[requestID] = expectedResponses{ + expected: numExpectedResponses, + } + return responseChan +} + // RegisterRequest registers an AppRequest with the timeout manager. // If RegisterResponse is not called before the timeout, HandleInbound is called with // an internally created AppRequestFailed message. -func (h *RelayerExternalHandler) RegisterRequest(reqID ids.RequestID) { +func (h *RelayerExternalHandler) RegisterAppRequest(reqID ids.RequestID) { inMsg := message.InboundAppError( reqID.NodeID, reqID.SourceChainID, reqID.RequestID, - common.ErrTimeout.Code, - common.ErrTimeout.Message, + avalancheCommon.ErrTimeout.Code, + avalancheCommon.ErrTimeout.Message, ) h.timeoutManager.Put(reqID, false, func() { h.HandleInbound(context.Background(), inMsg) @@ -164,6 +143,66 @@ func (h *RelayerExternalHandler) RegisterRequest(reqID ids.RequestID) { } // RegisterResponse registers an AppResponse with the timeout manager -func (h *RelayerExternalHandler) RegisterResponse(reqID ids.RequestID) { +func (h *RelayerExternalHandler) registerAppResponse(inboundMessage message.InboundMessage) { + h.lock.Lock() + defer h.lock.Unlock() + + // Extract the message fields + m := inboundMessage.Message() + + // Get the blockchainID from the message. + // Note: we should NOT call GetSourceBlockchainID; this is for cross-chain messages using the vm2 interface + // For normal app requests messages, the calls result in the same value, but if the relayer handles an + // inbound cross-chain app message, then we would get the incorrect chain ID. + blockchainID, err := message.GetChainID(m) + if err != nil { + h.log.Error("Could not get blockchainID from message") + inboundMessage.OnFinishedHandling() + return + } + sourceBlockchainID, err := message.GetSourceChainID(m) + if err != nil { + h.log.Error("Could not get sourceBlockchainID from message") + inboundMessage.OnFinishedHandling() + return + } + requestID, ok := message.GetRequestID(m) + if !ok { + h.log.Error("Could not get requestID from message") + inboundMessage.OnFinishedHandling() + return + } + + // Remove the timeout on the request + reqID := ids.RequestID{ + NodeID: inboundMessage.NodeID(), + SourceChainID: sourceBlockchainID, + DestinationChainID: blockchainID, + RequestID: requestID, + Op: byte(inboundMessage.Op()), + } h.timeoutManager.Remove(reqID) + + // Dispatch to the appropriate response channel + if responseChan, ok := h.responseChans[requestID]; ok { + responseChan <- inboundMessage + } else { + h.log.Debug("Could not find response channel for request", zap.Uint32("requestID", requestID)) + return + } + + // Check for the expected number of responses, and clear from the map if all expected responses have been received + // TODO: we can improve performance here by independently locking the response channel and response count maps + responses := h.responsesCount[requestID] + received := responses.received + 1 + if received == responses.expected { + close(h.responseChans[requestID]) + delete(h.responseChans, requestID) + delete(h.responsesCount, requestID) + } else { + h.responsesCount[requestID] = expectedResponses{ + expected: responses.expected, + received: received, + } + } } diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go index 601e4ce0..31d0f78c 100644 --- a/relayer/application_relayer.go +++ b/relayer/application_relayer.go @@ -8,6 +8,8 @@ import ( "context" "errors" "math/big" + "math/rand" + "sync" "time" "github.com/ava-labs/avalanchego/ids" @@ -25,9 +27,12 @@ import ( "github.com/ava-labs/awm-relayer/peers" "github.com/ava-labs/awm-relayer/relayer/checkpoint" "github.com/ava-labs/awm-relayer/utils" + "github.com/ava-labs/awm-relayer/vms" coreEthMsg "github.com/ava-labs/coreth/plugin/evm/message" msg "github.com/ava-labs/subnet-evm/plugin/evm/message" warpBackend "github.com/ava-labs/subnet-evm/warp" + "golang.org/x/sync/errgroup" + "go.uber.org/zap" ) @@ -49,36 +54,37 @@ var ( errNotEnoughConnectedStake = errors.New("failed to connect to a threshold of stake") ) -// applicationRelayers are created for each warp message to be relayed. -// They collect signatures from validators, aggregate them, -// and send the signed warp message to the destination chain. -// Each applicationRelayer runs in its own goroutine. -type applicationRelayer struct { +// ApplicationRelayers define a Warp message route from a specific source address on a specific source blockchain +// to a specific destination address on a specific destination blockchain. This routing information is +// encapsulated in [relayerID], which also represents the database key for an ApplicationRelayer. +type ApplicationRelayer struct { logger logging.Logger metrics *ApplicationRelayerMetrics network *peers.AppRequestNetwork messageCreator message.Creator - responseChan chan message.InboundMessage sourceBlockchain config.SourceBlockchain signingSubnetID ids.ID + destinationClient vms.DestinationClient relayerID database.RelayerID warpQuorum config.WarpQuorum checkpointManager *checkpoint.CheckpointManager + currentRequestID uint32 + lock *sync.RWMutex } -func newApplicationRelayer( +func NewApplicationRelayer( logger logging.Logger, metrics *ApplicationRelayerMetrics, network *peers.AppRequestNetwork, messageCreator message.Creator, - responseChan chan message.InboundMessage, relayerID database.RelayerID, db database.RelayerDatabase, ticker *utils.Ticker, + destinationClient vms.DestinationClient, sourceBlockchain config.SourceBlockchain, startingHeight uint64, cfg *config.Config, -) (*applicationRelayer, error) { +) (*ApplicationRelayer, error) { quorum, err := cfg.GetWarpQuorum(relayerID.DestinationBlockchainID) if err != nil { logger.Error( @@ -102,30 +108,90 @@ func newApplicationRelayer( checkpointManager := checkpoint.NewCheckpointManager(logger, db, sub, relayerID, startingHeight) checkpointManager.Run() - ar := applicationRelayer{ + ar := ApplicationRelayer{ logger: logger, metrics: metrics, network: network, messageCreator: messageCreator, - responseChan: responseChan, sourceBlockchain: sourceBlockchain, + destinationClient: destinationClient, relayerID: relayerID, signingSubnetID: signingSubnet, warpQuorum: quorum, checkpointManager: checkpointManager, + currentRequestID: rand.Uint32(), // TODONOW: pass via ctor + lock: &sync.RWMutex{}, } return &ar, nil } -func (r *applicationRelayer) relayMessage( - unsignedMessage *avalancheWarp.UnsignedMessage, +// Process [msgs] at height [height] by relaying each message to the destination chain. +// Checkpoints the height with the checkpoint manager when all messages are relayed. +// ProcessHeight is expected to be called for every block greater than or equal to the [startingHeight] provided in the constructor +func (r *ApplicationRelayer) ProcessHeight(height uint64, handlers []messages.MessageHandler, errChan chan error) { + var eg errgroup.Group + for _, handler := range handlers { + // Copy the loop variable to a local variable to avoid the loop variable being captured by the goroutine + // Once we upgrade to Go 1.22, we can use the loop variable directly in the goroutine + h := handler + eg.Go(func() error { + return r.ProcessMessage(h) + }) + } + if err := eg.Wait(); err != nil { + r.logger.Error( + "Failed to process block", + zap.Uint64("height", height), + zap.String("relayerID", r.relayerID.ID.String()), + zap.Error(err), + ) + errChan <- err + return + } + r.checkpointManager.StageCommittedHeight(height) + r.logger.Debug( + "Processed block", + zap.Uint64("height", height), + zap.String("sourceBlockchainID", r.relayerID.SourceBlockchainID.String()), + zap.String("relayerID", r.relayerID.ID.String()), + zap.Int("numMessages", len(handlers)), + ) +} + +// Relays a message to the destination chain. Does not checkpoint the height. +func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) error { + // Increment the request ID. Make sure we don't hold the lock while we relay the message. + r.lock.Lock() + r.currentRequestID++ + reqID := r.currentRequestID + r.lock.Unlock() + + err := r.relayMessage( + reqID, + handler, + true, + ) + + return err +} + +func (r *ApplicationRelayer) RelayerID() database.RelayerID { + return r.relayerID +} + +func (r *ApplicationRelayer) relayMessage( requestID uint32, - messageManager messages.MessageManager, - blockNumber uint64, + handler messages.MessageHandler, useAppRequestNetwork bool, ) error { - shouldSend, err := messageManager.ShouldSendMessage(unsignedMessage, r.relayerID.DestinationBlockchainID) + r.logger.Debug( + "Relaying message", + zap.Uint32("requestID", requestID), + zap.String("sourceBlockchainID", r.sourceBlockchain.BlockchainID), + zap.String("relayerID", r.relayerID.ID.String()), + ) + shouldSend, err := handler.ShouldSendMessage(r.destinationClient) if err != nil { r.logger.Error( "Failed to check if message should be sent", @@ -136,9 +202,9 @@ func (r *applicationRelayer) relayMessage( } if !shouldSend { r.logger.Info("Message should not be sent") - r.checkpointManager.Finished(blockNumber) return nil } + unsignedMessage := handler.GetUnsignedMessage() startCreateSignedMessageTime := time.Now() // Query nodes on the origin chain for signatures, and construct the signed warp message. @@ -168,7 +234,7 @@ func (r *applicationRelayer) relayMessage( // create signed message latency (ms) r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds())) - err = messageManager.SendMessage(signedMessage, r.relayerID.DestinationBlockchainID) + err = handler.SendMessage(signedMessage, r.destinationClient) if err != nil { r.logger.Error( "Failed to send warp message", @@ -183,15 +249,13 @@ func (r *applicationRelayer) relayMessage( ) r.incSuccessfulRelayMessageCount() - // Update the database with the latest processed block height - r.checkpointManager.Finished(blockNumber) return nil } // createSignedMessage fetches the signed Warp message from the source chain via RPC. // Each VM may implement their own RPC method to construct the aggregate signature, which // will need to be accounted for here. -func (r *applicationRelayer) createSignedMessage(unsignedMessage *avalancheWarp.UnsignedMessage) (*avalancheWarp.Message, error) { +func (r *ApplicationRelayer) createSignedMessage(unsignedMessage *avalancheWarp.UnsignedMessage) (*avalancheWarp.Message, error) { r.logger.Info("Fetching aggregate signature from the source chain validators via API") // TODO: To properly support this, we should provide a dedicated Warp API endpoint in the config uri := utils.StripFromString(r.sourceBlockchain.RPCEndpoint.BaseURL, "/ext") @@ -252,7 +316,7 @@ func (r *applicationRelayer) createSignedMessage(unsignedMessage *avalancheWarp. } // createSignedMessageAppRequest collects signatures from nodes by directly querying them via AppRequest, then aggregates the signatures, and constructs the signed warp message. -func (r *applicationRelayer) createSignedMessageAppRequest(unsignedMessage *avalancheWarp.UnsignedMessage, requestID uint32) (*avalancheWarp.Message, error) { +func (r *ApplicationRelayer) createSignedMessageAppRequest(unsignedMessage *avalancheWarp.UnsignedMessage, requestID uint32) (*avalancheWarp.Message, error) { r.logger.Info("Fetching aggregate signature from the source chain validators via AppRequest") connectedValidators, err := r.network.ConnectToCanonicalValidators(r.signingSubnetID) if err != nil { @@ -277,8 +341,6 @@ func (r *applicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval return nil, errNotEnoughConnectedStake } - // Construct the request - // Make sure to use the correct codec var reqBytes []byte if r.sourceBlockchain.GetSubnetID() == constants.PrimaryNetworkID { @@ -301,6 +363,7 @@ func (r *applicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval return nil, err } + // Construct the AppRequest outMsg, err := r.messageCreator.AppRequest(unsignedMessage.SourceChainID, requestID, peers.DefaultAppRequestTimeout, reqBytes) if err != nil { r.logger.Error( @@ -314,7 +377,6 @@ func (r *applicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval accumulatedSignatureWeight := big.NewInt(0) signatureMap := make(map[int]blsSignatureBuf) - for attempt := 1; attempt <= maxRelayerQueryAttempts; attempt++ { responsesExpected := len(connectedValidators.ValidatorSet) - len(signatureMap) r.logger.Debug( @@ -349,8 +411,9 @@ func (r *applicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval RequestID: requestID, Op: byte(message.AppResponseOp), } - r.network.Handler.RegisterRequest(reqID) + r.network.Handler.RegisterAppRequest(reqID) } + responseChan := r.network.Handler.RegisterRequestID(requestID, vdrSet.Len()) sentTo := r.network.Network.Send(outMsg, vdrSet, r.sourceBlockchain.GetSubnetID(), subnets.NoOpAllower) r.logger.Debug( @@ -373,95 +436,26 @@ func (r *applicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval if responsesExpected > 0 { // Handle the responses. For each response, we need to call response.OnFinishedHandling() exactly once. // Wrap the loop body in an anonymous function so that we do so on each loop iteration - // TODO: In order to run this concurrently, we need to route to each application relayer from the relayer responseChan - for response := range r.responseChan { + for response := range responseChan { r.logger.Debug( "Processing response from node", zap.String("nodeID", response.NodeID().String()), ) - // This anonymous function attempts to create a signed warp message from the accumulated responses - // Returns an error only if a non-recoverable error occurs, otherwise returns (nil, nil) to continue processing responses - // When a non-nil signedMsg is returned, createSignedMessage itself returns - signedMsg, err := func() (*avalancheWarp.Message, error) { - defer response.OnFinishedHandling() - - // Check if this is an expected response. - m := response.Message() - rcvReqID, ok := message.GetRequestID(m) - if !ok { - // This should never occur, since inbound message validity is already checked by the inbound handler - r.logger.Error("Could not get requestID from message") - return nil, nil - } - nodeID := response.NodeID() - if !sentTo.Contains(nodeID) || rcvReqID != requestID { - r.logger.Debug("Skipping irrelevant app response") - return nil, nil - } - - // Count the relevant app message - responseCount++ - - // If we receive an AppRequestFailed, then the request timed out. - // We still want to increment responseCount, since we are no longer expecting a response from that node. - if response.Op() == message.AppErrorOp { - r.logger.Debug("Request timed out") - return nil, nil - } - - validator, vdrIndex := connectedValidators.GetValidator(nodeID) - signature, valid := r.isValidSignatureResponse(unsignedMessage, response, validator.PublicKey) - if valid { - r.logger.Debug( - "Got valid signature response", - zap.String("nodeID", nodeID.String()), - ) - signatureMap[vdrIndex] = signature - accumulatedSignatureWeight.Add(accumulatedSignatureWeight, new(big.Int).SetUint64(validator.Weight)) - } else { - r.logger.Debug( - "Got invalid signature response", - zap.String("nodeID", nodeID.String()), - ) - return nil, nil - } - - // As soon as the signatures exceed the stake weight threshold we try to aggregate and send the transaction. - if utils.CheckStakeWeightExceedsThreshold( - accumulatedSignatureWeight, - connectedValidators.TotalValidatorWeight, - r.warpQuorum.QuorumNumerator, - r.warpQuorum.QuorumDenominator, - ) { - aggSig, vdrBitSet, err := r.aggregateSignatures(signatureMap) - if err != nil { - r.logger.Error( - "Failed to aggregate signature.", - zap.String("destinationBlockchainID", r.relayerID.DestinationBlockchainID.String()), - zap.Error(err), - ) - return nil, err - } - - signedMsg, err := avalancheWarp.NewMessage(unsignedMessage, &avalancheWarp.BitSetSignature{ - Signers: vdrBitSet.Bytes(), - Signature: *(*[bls.SignatureLen]byte)(bls.SignatureToBytes(aggSig)), - }) - if err != nil { - r.logger.Error( - "Failed to create new signed message", - zap.Error(err), - ) - return nil, err - } - return signedMsg, nil - } - // Not enough signatures, continue processing messages - return nil, nil - }() + signedMsg, relevant, err := r.handleResponse( + response, + sentTo, + requestID, + connectedValidators, + unsignedMessage, + signatureMap, + accumulatedSignatureWeight, + ) if err != nil { return nil, err } + if relevant { + responseCount++ + } // If we have sufficient signatures, return here. if signedMsg != nil { r.logger.Info( @@ -491,9 +485,97 @@ func (r *applicationRelayer) createSignedMessageAppRequest(unsignedMessage *aval return nil, errNotEnoughSignatures } +// Attempts to create a signed warp message from the accumulated responses. +// Returns a non-nil Warp message if [accumulatedSignatureWeight] exceeds the signature verification threshold. +// Returns false in the second return parameter if the app response is not relevant to the current signature aggregation request. +// Returns an error only if a non-recoverable error occurs, otherwise returns a nil error to continue processing responses. +func (r *ApplicationRelayer) handleResponse( + response message.InboundMessage, + sentTo set.Set[ids.NodeID], + requestID uint32, + connectedValidators *peers.ConnectedCanonicalValidators, + unsignedMessage *avalancheWarp.UnsignedMessage, + signatureMap map[int]blsSignatureBuf, + accumulatedSignatureWeight *big.Int, +) (*avalancheWarp.Message, bool, error) { + // Regardless of the response's relevance, call it's finished handler once this function returns + defer response.OnFinishedHandling() + + // Check if this is an expected response. + m := response.Message() + rcvReqID, ok := message.GetRequestID(m) + if !ok { + // This should never occur, since inbound message validity is already checked by the inbound handler + r.logger.Error("Could not get requestID from message") + return nil, false, nil + } + nodeID := response.NodeID() + if !sentTo.Contains(nodeID) || rcvReqID != requestID { + r.logger.Debug("Skipping irrelevant app response") + return nil, false, nil + } + + // If we receive an AppRequestFailed, then the request timed out. + // This is still a relevant response, since we are no longer expecting a response from that node. + if response.Op() == message.AppErrorOp { + r.logger.Debug("Request timed out") + return nil, true, nil + } + + validator, vdrIndex := connectedValidators.GetValidator(nodeID) + signature, valid := r.isValidSignatureResponse(unsignedMessage, response, validator.PublicKey) + if valid { + r.logger.Debug( + "Got valid signature response", + zap.String("nodeID", nodeID.String()), + ) + signatureMap[vdrIndex] = signature + accumulatedSignatureWeight.Add(accumulatedSignatureWeight, new(big.Int).SetUint64(validator.Weight)) + } else { + r.logger.Debug( + "Got invalid signature response", + zap.String("nodeID", nodeID.String()), + ) + return nil, true, nil + } + + // As soon as the signatures exceed the stake weight threshold we try to aggregate and send the transaction. + if utils.CheckStakeWeightExceedsThreshold( + accumulatedSignatureWeight, + connectedValidators.TotalValidatorWeight, + r.warpQuorum.QuorumNumerator, + r.warpQuorum.QuorumDenominator, + ) { + aggSig, vdrBitSet, err := r.aggregateSignatures(signatureMap) + if err != nil { + r.logger.Error( + "Failed to aggregate signature.", + zap.String("destinationBlockchainID", r.relayerID.DestinationBlockchainID.String()), + zap.Error(err), + ) + return nil, true, err + } + + signedMsg, err := avalancheWarp.NewMessage(unsignedMessage, &avalancheWarp.BitSetSignature{ + Signers: vdrBitSet.Bytes(), + Signature: *(*[bls.SignatureLen]byte)(bls.SignatureToBytes(aggSig)), + }) + if err != nil { + r.logger.Error( + "Failed to create new signed message", + zap.Error(err), + ) + return nil, true, err + } + return signedMsg, true, nil + } + // Not enough signatures, continue processing messages + return nil, true, nil +} + // isValidSignatureResponse tries to generate a signature from the peer.AsyncResponse, then verifies the signature against the node's public key. // If we are unable to generate the signature or verify correctly, false will be returned to indicate no valid signature was found in response. -func (r *applicationRelayer) isValidSignatureResponse( +func (r *ApplicationRelayer) isValidSignatureResponse( unsignedMessage *avalancheWarp.UnsignedMessage, response message.InboundMessage, pubKey *bls.PublicKey, @@ -558,7 +640,7 @@ func (r *applicationRelayer) isValidSignatureResponse( // aggregateSignatures constructs a BLS aggregate signature from the collected validator signatures. Also returns a bit set representing the // validators that are represented in the aggregate signature. The bit set is in canonical validator order. -func (r *applicationRelayer) aggregateSignatures(signatureMap map[int]blsSignatureBuf) (*bls.Signature, set.Bits, error) { +func (r *ApplicationRelayer) aggregateSignatures(signatureMap map[int]blsSignatureBuf) (*bls.Signature, set.Bits, error) { // Aggregate the signatures signatures := make([]*bls.Signature, 0, len(signatureMap)) vdrBitSet := set.NewBits() @@ -591,7 +673,7 @@ func (r *applicationRelayer) aggregateSignatures(signatureMap map[int]blsSignatu // Metrics // -func (r *applicationRelayer) incSuccessfulRelayMessageCount() { +func (r *ApplicationRelayer) incSuccessfulRelayMessageCount() { r.metrics.successfulRelayMessageCount. WithLabelValues( r.relayerID.DestinationBlockchainID.String(), @@ -599,7 +681,7 @@ func (r *applicationRelayer) incSuccessfulRelayMessageCount() { r.sourceBlockchain.GetSubnetID().String()).Inc() } -func (r *applicationRelayer) incFailedRelayMessageCount(failureReason string) { +func (r *ApplicationRelayer) incFailedRelayMessageCount(failureReason string) { r.metrics.failedRelayMessageCount. WithLabelValues( r.relayerID.DestinationBlockchainID.String(), @@ -608,7 +690,7 @@ func (r *applicationRelayer) incFailedRelayMessageCount(failureReason string) { failureReason).Inc() } -func (r *applicationRelayer) setCreateSignedMessageLatencyMS(latency float64) { +func (r *ApplicationRelayer) setCreateSignedMessageLatencyMS(latency float64) { r.metrics.createSignedMessageLatencyMS. WithLabelValues( r.relayerID.DestinationBlockchainID.String(), diff --git a/relayer/checkpoint/checkpoint.go b/relayer/checkpoint/checkpoint.go index 145b4316..6d0e0e08 100644 --- a/relayer/checkpoint/checkpoint.go +++ b/relayer/checkpoint/checkpoint.go @@ -19,15 +19,13 @@ import ( // type CheckpointManager struct { - logger logging.Logger - database database.RelayerDatabase - writeSignal chan struct{} - relayerID database.RelayerID - queuedHeightsAndMessages map[uint64]*messageCounter - committedHeight uint64 - lock *sync.RWMutex - pendingCommits *utils.UInt64Heap - finished chan uint64 + logger logging.Logger + database database.RelayerDatabase + writeSignal chan struct{} + relayerID database.RelayerID + committedHeight uint64 + lock *sync.RWMutex + pendingCommits *utils.UInt64Heap } func NewCheckpointManager( @@ -39,28 +37,26 @@ func NewCheckpointManager( ) *CheckpointManager { h := &utils.UInt64Heap{} heap.Init(h) + logger.Info( + "Creating checkpoint manager", + zap.String("relayerID", relayerID.ID.String()), + zap.Uint64("startingHeight", startingHeight), + ) return &CheckpointManager{ - logger: logger, - database: database, - writeSignal: writeSignal, - relayerID: relayerID, - queuedHeightsAndMessages: make(map[uint64]*messageCounter), - committedHeight: startingHeight, - lock: &sync.RWMutex{}, - pendingCommits: h, - finished: make(chan uint64), + logger: logger, + database: database, + writeSignal: writeSignal, + relayerID: relayerID, + committedHeight: startingHeight, + lock: &sync.RWMutex{}, + pendingCommits: h, } } func (cm *CheckpointManager) Run() { - go cm.listenForFinishedMessageRelays() go cm.listenForWriteSignal() } -func (cm *CheckpointManager) Finished(blockNumber uint64) { - cm.finished <- blockNumber -} - func (cm *CheckpointManager) writeToDatabase() { cm.lock.RLock() defer cm.lock.RUnlock() @@ -102,62 +98,21 @@ func (cm *CheckpointManager) listenForWriteSignal() { } } -// Increments the processed message count at the specified height. -// Once the processed message count equals the number of expected messages in the block, -// the block height is staged to be committed to the database. -func (cm *CheckpointManager) incrementProcessedMessageCounter(height uint64) { - cm.lock.Lock() - defer cm.lock.Unlock() - counter, ok := cm.queuedHeightsAndMessages[height] - if !ok { - // This is expected for Warp messages that are not associated with the height greater than the latest processed block height. - // For example, on startup it is possible for an Application Relayer to re-process messages for a height that has already been committed. - // This is also the case for manual Warp messages that are processed out-of-band. - cm.logger.Debug( - "Pending height not found", - zap.Uint64("height", height), - zap.String("relayerID", cm.relayerID.ID.String()), - ) - return - } - // Increment the processed message count in-place in the queuedHeightsAndMessages map - counter.processedMessages++ - cm.logger.Debug( - "Received finished signal for block", - zap.Uint64("height", height), - zap.String("relayerID", cm.relayerID.ID.String()), - zap.Uint64("processedMessages", counter.processedMessages), - zap.Uint64("totalMessages", counter.totalMessages), - ) - if counter.processedMessages == counter.totalMessages { - cm.stageCommittedHeight(height) - delete(cm.queuedHeightsAndMessages, height) - } -} - -// handleFinishedRelays listens for finished signals from the application relayer, and commits the -// height once all messages have been processed. -// This function should only be called once. -func (cm *CheckpointManager) listenForFinishedMessageRelays() { - for height := range cm.finished { - cm.incrementProcessedMessageCounter(height) - } -} - -// stageCommittedHeight queues a height to be written to the database. +// StageCommittedHeight queues a height to be written to the database. // Heights are committed in sequence, so if height is not exactly one // greater than the current committedHeight, it is instead cached in memory // to potentially be committed later. -// Requires that cm.lock be held -func (cm *CheckpointManager) stageCommittedHeight(height uint64) { +func (cm *CheckpointManager) StageCommittedHeight(height uint64) { + cm.lock.Lock() + defer cm.lock.Unlock() if height <= cm.committedHeight { - cm.logger.Fatal( - "Attempting to commit height less than or equal to the committed height", + cm.logger.Debug( + "Attempting to commit height less than or equal to the committed height. Skipping.", zap.Uint64("height", height), zap.Uint64("committedHeight", cm.committedHeight), zap.String("relayerID", cm.relayerID.ID.String()), ) - panic("attempting to commit height less than or equal to the committed height") + return } // First push the height onto the pending commits min heap @@ -183,44 +138,3 @@ func (cm *CheckpointManager) stageCommittedHeight(height uint64) { } } } - -// PrepareHeight sets the total number of messages to be processed at a given height. -// Once all messages have been processed, the height is eligible to be committed. -// It is up to the caller to determine if a height is eligible to be committed. -// This function is thread safe. -func (cm *CheckpointManager) PrepareHeight(height uint64, totalMessages uint64) { - cm.lock.Lock() - defer cm.lock.Unlock() - // Heights less than or equal to the committed height are not candidates to write to the database. - // This is to ensure that writes are strictly increasing. - if height <= cm.committedHeight { - cm.logger.Debug( - "Skipping height", - zap.Uint64("height", height), - zap.Uint64("committedHeight", cm.committedHeight), - zap.String("relayerID", cm.relayerID.ID.String()), - ) - return - } - cm.logger.Debug( - "Preparing height", - zap.Uint64("height", height), - zap.Uint64("totalMessages", totalMessages), - zap.String("relayerID", cm.relayerID.ID.String()), - ) - // Short circuit to staging the height if there are no messages to process - if totalMessages == 0 { - cm.stageCommittedHeight(height) - return - } - cm.queuedHeightsAndMessages[height] = &messageCounter{ - totalMessages: totalMessages, - processedMessages: 0, - } -} - -// Helper type -type messageCounter struct { - totalMessages uint64 - processedMessages uint64 -} diff --git a/relayer/checkpoint/checkpoint_test.go b/relayer/checkpoint/checkpoint_test.go index 5c7aac1f..155616a9 100644 --- a/relayer/checkpoint/checkpoint_test.go +++ b/relayer/checkpoint/checkpoint_test.go @@ -67,7 +67,7 @@ func TestCommitHeight(t *testing.T) { heap.Init(test.pendingHeights) cm.pendingCommits = test.pendingHeights cm.committedHeight = test.currentMaxHeight - cm.stageCommittedHeight(test.commitHeight) + cm.StageCommittedHeight(test.commitHeight) require.Equal(t, test.expectedMaxHeight, cm.committedHeight, test.name) } } diff --git a/relayer/listener.go b/relayer/listener.go index bab1d6a5..57449ddc 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -8,18 +8,17 @@ import ( "fmt" "math/big" "math/rand" + "sync" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/message" "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/ethclient" "github.com/ava-labs/awm-relayer/messages" - "github.com/ava-labs/awm-relayer/peers" + offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" + "github.com/ava-labs/awm-relayer/messages/teleporter" relayerTypes "github.com/ava-labs/awm-relayer/types" - "github.com/ava-labs/awm-relayer/utils" vms "github.com/ava-labs/awm-relayer/vms" "github.com/ethereum/go-ethereum/common" "go.uber.org/atomic" @@ -35,32 +34,28 @@ const ( // Listener handles all messages sent from a given source chain type Listener struct { - Subscriber vms.Subscriber - currentRequestID uint32 - responseChan chan message.InboundMessage - contractMessage vms.ContractMessage - messageManagers map[common.Address]messages.MessageManager - logger logging.Logger - sourceBlockchain config.SourceBlockchain - catchUpResultChan chan bool - healthStatus *atomic.Bool - globalConfig *config.Config - applicationRelayers map[common.Hash]*applicationRelayer - ethClient ethclient.Client + Subscriber vms.Subscriber + requestIDLock *sync.Mutex + currentRequestID uint32 + contractMessage vms.ContractMessage + messageHandlerFactories map[common.Address]messages.MessageHandlerFactory + logger logging.Logger + sourceBlockchain config.SourceBlockchain + catchUpResultChan chan bool + healthStatus *atomic.Bool + globalConfig *config.Config + applicationRelayers map[common.Hash]*ApplicationRelayer + ethClient ethclient.Client } func NewListener( logger logging.Logger, - metrics *ApplicationRelayerMetrics, - db database.RelayerDatabase, - ticker *utils.Ticker, sourceBlockchain config.SourceBlockchain, - network *peers.AppRequestNetwork, - responseChan chan message.InboundMessage, - destinationClients map[ids.ID]vms.DestinationClient, - messageCreator message.Creator, relayerHealth *atomic.Bool, - cfg *config.Config, + globalConfig *config.Config, + applicationRelayers map[common.Hash]*ApplicationRelayer, + startingHeight uint64, + ethClient ethclient.Client, ) (*Listener, error) { blockchainID, err := ids.FromString(sourceBlockchain.BlockchainID) if err != nil { @@ -86,26 +81,30 @@ func NewListener( } sub := vms.NewSubscriber(logger, config.ParseVM(sourceBlockchain.VM), blockchainID, ethWSClient) - ethRPCClient, err := ethclient.DialWithConfig( - context.Background(), - sourceBlockchain.RPCEndpoint.BaseURL, - sourceBlockchain.RPCEndpoint.HTTPHeaders, - sourceBlockchain.RPCEndpoint.QueryParams, - ) - if err != nil { - logger.Error( - "Failed to connect to node via RPC", - zap.String("blockchainID", blockchainID.String()), - zap.Error(err), - ) - return nil, err - } - // Create message managers for each supported message protocol - messageManagers := make(map[common.Address]messages.MessageManager) - for addressStr, config := range sourceBlockchain.MessageContracts { + messageHandlerFactories := make(map[common.Address]messages.MessageHandlerFactory) + for addressStr, cfg := range sourceBlockchain.MessageContracts { address := common.HexToAddress(addressStr) - messageManager, err := messages.NewMessageManager(logger, address, config, destinationClients) + format := cfg.MessageFormat + var ( + m messages.MessageHandlerFactory + err error + ) + switch config.ParseMessageProtocol(format) { + case config.TELEPORTER: + m, err = teleporter.NewMessageHandlerFactory( + logger, + address, + cfg, + ) + case config.OFF_CHAIN_REGISTRY: + m, err = offchainregistry.NewMessageHandlerFactory( + logger, + cfg, + ) + default: + m, err = nil, fmt.Errorf("invalid message format %s", format) + } if err != nil { logger.Error( "Failed to create message manager", @@ -113,7 +112,7 @@ func NewListener( ) return nil, err } - messageManagers[address] = messageManager + messageHandlerFactories[address] = m } // Marks when the listener has finished the catch-up process on startup. @@ -124,62 +123,6 @@ func NewListener( // scenario. catchUpResultChan := make(chan bool, 1) - currentHeight, err := ethRPCClient.BlockNumber(context.Background()) - if err != nil { - logger.Error( - "Failed to get current block height", - zap.Error(err), - ) - return nil, err - } - - // Create the application relayers - applicationRelayers := make(map[common.Hash]*applicationRelayer) - minHeight := uint64(0) - - for _, relayerID := range database.GetSourceBlockchainRelayerIDs(&sourceBlockchain) { - height, err := database.CalculateStartingBlockHeight( - logger, - db, - relayerID, - sourceBlockchain.ProcessHistoricalBlocksFromHeight, - currentHeight, - ) - if err != nil { - logger.Error( - "Failed to calculate starting block height", - zap.String("relayerID", relayerID.ID.String()), - zap.Error(err), - ) - return nil, err - } - if minHeight == 0 || height < minHeight { - minHeight = height - } - applicationRelayer, err := newApplicationRelayer( - logger, - metrics, - network, - messageCreator, - responseChan, - relayerID, - db, - ticker, - sourceBlockchain, - height, - cfg, - ) - if err != nil { - logger.Error( - "Failed to create application relayer", - zap.String("relayerID", relayerID.ID.String()), - zap.Error(err), - ) - return nil, err - } - applicationRelayers[relayerID.ID] = applicationRelayer - } - logger.Info( "Creating relayer", zap.String("subnetID", sourceBlockchain.GetSubnetID().String()), @@ -188,18 +131,18 @@ func NewListener( zap.String("blockchainIDHex", sourceBlockchain.GetBlockchainID().Hex()), ) lstnr := Listener{ - Subscriber: sub, - currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision - responseChan: responseChan, - contractMessage: vms.NewContractMessage(logger, sourceBlockchain), - messageManagers: messageManagers, - logger: logger, - sourceBlockchain: sourceBlockchain, - catchUpResultChan: catchUpResultChan, - healthStatus: relayerHealth, - globalConfig: cfg, - applicationRelayers: applicationRelayers, - ethClient: ethRPCClient, + Subscriber: sub, + requestIDLock: &sync.Mutex{}, + currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision + contractMessage: vms.NewContractMessage(logger, sourceBlockchain), + messageHandlerFactories: messageHandlerFactories, + logger: logger, + sourceBlockchain: sourceBlockchain, + catchUpResultChan: catchUpResultChan, + healthStatus: relayerHealth, + globalConfig: globalConfig, + applicationRelayers: applicationRelayers, + ethClient: ethClient, } // Open the subscription. We must do this before processing any missed messages, otherwise we may miss an incoming message @@ -217,7 +160,7 @@ func NewListener( // Process historical blocks in a separate goroutine so that the main processing loop can // start processing new blocks as soon as possible. Otherwise, it's possible for // ProcessFromHeight to overload the message queue and cause a deadlock. - go sub.ProcessFromHeight(big.NewInt(0).SetUint64(minHeight), lstnr.catchUpResultChan) + go sub.ProcessFromHeight(big.NewInt(0).SetUint64(startingHeight), lstnr.catchUpResultChan) } else { lstnr.logger.Info( "processed-missed-blocks set to false, starting processing from chain head", @@ -233,8 +176,16 @@ func NewListener( // On subscriber error, attempts to reconnect and errors if unable. // Exits if context is cancelled by another goroutine. func (lstnr *Listener) ProcessLogs(ctx context.Context) error { + // Error channel for application relayer errors + errChan := make(chan error) for { select { + case err := <-errChan: + lstnr.healthStatus.Store(false) + lstnr.logger.Error( + "Received error from application relayer", + zap.Error(err), + ) case catchUpResult, ok := <-lstnr.catchUpResultChan: // As soon as we've received anything on the channel, there are no more values expected. // The expected case is that the channel is closed by the subscriber after writing a value to it, @@ -259,6 +210,8 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error { return fmt.Errorf("failed to catch up on historical blocks") } case blockHeader := <-lstnr.Subscriber.Headers(): + // Parse the logs in the block, and group by application relayer + block, err := relayerTypes.NewWarpBlockInfo(blockHeader, lstnr.ethClient) if err != nil { lstnr.logger.Error( @@ -275,23 +228,10 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error { zap.Uint64("blockNumber", block.BlockNumber), ) - // Iterate over the Warp logs in two passes: - // The first pass extracts the information needed to relay from the log, but does not initiate relaying - // This is so that the number of messages to be processed can be registered with the database before - // any messages are processed. - // The second pass dispatches the messages to the application relayers for processing - expectedMessages := make(map[database.RelayerID]uint64) - var msgsInfo []*parsedMessageInfo - for _, warpLog := range block.WarpLogs { - warpLogInfo, err := relayerTypes.NewWarpLogInfo(warpLog) - if err != nil { - lstnr.logger.Error( - "Failed to create warp log info", - zap.Error(err), - ) - continue - } - msgInfo, err := lstnr.parseMessage(warpLogInfo) + // Register each message in the block with the appropriate application relayer + messageHandlers := make(map[common.Hash][]messages.MessageHandler) + for _, warpLogInfo := range block.Messages { + appRelayer, handler, err := lstnr.GetAppRelayerMessageHandler(warpLogInfo) if err != nil { lstnr.logger.Error( "Failed to parse message", @@ -300,38 +240,21 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error { ) continue } - if msgInfo == nil { - lstnr.logger.Debug( - "Application relayer not found. Skipping message relay.", - zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - ) + if appRelayer == nil { + lstnr.logger.Debug("Application relayer not found. Skipping message relay") continue } - msgsInfo = append(msgsInfo, msgInfo) - expectedMessages[msgInfo.applicationRelayer.relayerID]++ + messageHandlers[appRelayer.relayerID.ID] = append(messageHandlers[appRelayer.relayerID.ID], handler) } + // Initiate message relay of all registered messages for _, appRelayer := range lstnr.applicationRelayers { - // Prepare the each application relayer's database key with the number - // of expected messages. If no messages are found in the above loop, then - // totalMessages will be 0 - totalMessages := expectedMessages[appRelayer.relayerID] - appRelayer.checkpointManager.PrepareHeight(block.BlockNumber, totalMessages) - } - for _, msgInfo := range msgsInfo { - lstnr.logger.Info( - "Relaying message", - zap.String("sourceBlockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - zap.String("warpMessageID", msgInfo.unsignedMessage.ID().String()), - ) - err := lstnr.dispatchToApplicationRelayer(msgInfo, block.BlockNumber) - if err != nil { - lstnr.logger.Error( - "Error relaying message", - zap.String("sourceBlockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - zap.Error(err), - ) - continue - } + // Dispatch all messages in the block to the appropriate application relayer. + // An empty slice is still a valid argument to ProcessHeight; in this case the height is immediately committed. + handlers := messageHandlers[appRelayer.relayerID.ID] + + // Process the height async. This is safe because the ApplicationRelayer maintains the threadsafe + // invariant that heights are committed to the database one at a time, in order, with no gaps. + go appRelayer.ProcessHeight(block.BlockNumber, handlers, errChan) } case err := <-lstnr.Subscriber.Err(): lstnr.healthStatus.Store(false) @@ -342,7 +265,7 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error { ) // TODO try to resubscribe in perpetuity once we have a mechanism for refreshing state // variables such as Quorum values and processing missed blocks. - err = lstnr.ReconnectToSubscriber() + err = lstnr.reconnectToSubscriber() if err != nil { lstnr.logger.Error( "Relayer goroutine exiting.", @@ -363,7 +286,7 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error { } // Sets the listener health status to false while attempting to reconnect. -func (lstnr *Listener) ReconnectToSubscriber() error { +func (lstnr *Listener) reconnectToSubscriber() error { // Attempt to reconnect the subscription err := lstnr.Subscriber.Subscribe(maxResubscribeAttempts) if err != nil { @@ -375,29 +298,6 @@ func (lstnr *Listener) ReconnectToSubscriber() error { return nil } -// RouteMessage relays a single warp message to the destination chain. -// Warp message relay requests from the same origin chain are processed serially -func (lstnr *Listener) RouteManualWarpMessage(warpLogInfo *relayerTypes.WarpLogInfo) error { - parsedMessageInfo, err := lstnr.parseMessage(warpLogInfo) - if err != nil { - lstnr.logger.Error( - "Failed to parse message", - zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - zap.Error(err), - ) - return err - } - if parsedMessageInfo == nil { - lstnr.logger.Debug( - "Application relayer not found. Skipping message relay.", - zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - ) - return nil - } - - return lstnr.dispatchToApplicationRelayer(parsedMessageInfo, 0) -} - // Unpacks the Warp message and fetches the appropriate application relayer // Checks for the following registered keys. At most one of these keys should be registered. // 1. An exact match on sourceBlockchainID, destinationBlockchainID, originSenderAddress, and destinationAddress @@ -409,8 +309,7 @@ func (lstnr *Listener) getApplicationRelayer( originSenderAddress common.Address, destinationBlockchainID ids.ID, destinationAddress common.Address, - messageManager messages.MessageManager, -) *applicationRelayer { +) *ApplicationRelayer { // Check for an exact match applicationRelayerID := database.CalculateRelayerID( sourceBlockchainID, @@ -464,47 +363,43 @@ func (lstnr *Listener) getApplicationRelayer( return nil } -// Helper type and function to extract the information needed to relay a message -// From the raw log information -type parsedMessageInfo struct { - unsignedMessage *avalancheWarp.UnsignedMessage - messageManager messages.MessageManager - applicationRelayer *applicationRelayer -} - -func (lstnr *Listener) parseMessage(warpLogInfo *relayerTypes.WarpLogInfo) ( - *parsedMessageInfo, +// Returns the ApplicationRelayer that is configured to handle this message, as well as a one-time MessageHandler +// instance that the ApplicationRelayer uses to relay this specific message. +// The MessageHandler and ApplicationRelayer are decoupled to support batch workflows in which a single ApplicationRelayer +// processes multiple messages (using their corresponding MessageHandlers) in a single shot. +func (lstnr *Listener) GetAppRelayerMessageHandler(warpMessageInfo *relayerTypes.WarpMessageInfo) ( + *ApplicationRelayer, + messages.MessageHandler, error, ) { // Check that the warp message is from a supported message protocol contract address. - messageManager, supportedMessageProtocol := lstnr.messageManagers[warpLogInfo.SourceAddress] + messageHandlerFactory, supportedMessageProtocol := lstnr.messageHandlerFactories[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 listener instance. lstnr.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, nil + return nil, nil, nil } - - // Unpack the VM message bytes into a Warp message - unsignedMessage, err := lstnr.contractMessage.UnpackWarpMessage(warpLogInfo.UnsignedMsgBytes) + messageHandler, err := messageHandlerFactory.NewMessageHandler(warpMessageInfo.UnsignedMessage) if err != nil { lstnr.logger.Error( - "Failed to unpack sender message", + "Failed to create message handler", zap.Error(err), ) - return nil, err + return nil, nil, err } + // Fetch the message delivery data - sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, err := messageManager.GetMessageRoutingInfo(unsignedMessage) + sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, err := messageHandler.GetMessageRoutingInfo() if err != nil { lstnr.logger.Error( "Failed to get message routing information", zap.Error(err), ) - return nil, err + return nil, nil, err } lstnr.logger.Info( @@ -513,45 +408,51 @@ func (lstnr *Listener) parseMessage(warpLogInfo *relayerTypes.WarpLogInfo) ( zap.String("originSenderAddress", originSenderAddress.String()), zap.String("destinationBlockchainID", destinationBlockchainID.String()), zap.String("destinationAddress", destinationAddress.String()), - zap.String("warpMessageID", unsignedMessage.ID().String()), + zap.String("warpMessageID", warpMessageInfo.UnsignedMessage.ID().String()), ) - applicationRelayer := lstnr.getApplicationRelayer( + appRelayer := lstnr.getApplicationRelayer( sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, - messageManager, ) - if applicationRelayer == nil { - return nil, nil + if appRelayer == nil { + return nil, nil, nil } - return &parsedMessageInfo{ - unsignedMessage: unsignedMessage, - messageManager: messageManager, - applicationRelayer: applicationRelayer, - }, nil + return appRelayer, messageHandler, nil } -func (lstnr *Listener) dispatchToApplicationRelayer(parsedMessageInfo *parsedMessageInfo, blockNumber uint64) error { - // TODO: Add a config option to use the Warp API, instead of hardcoding to the app request network here - err := parsedMessageInfo.applicationRelayer.relayMessage( - parsedMessageInfo.unsignedMessage, - lstnr.currentRequestID, - parsedMessageInfo.messageManager, - blockNumber, - true, - ) - if err != nil { - lstnr.logger.Error( - "Failed to run application relayer", - zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - zap.String("warpMessageID", parsedMessageInfo.unsignedMessage.ID().String()), - zap.Error(err), +func (lstnr *Listener) ProcessManualWarpMessages( + logger logging.Logger, + manualWarpMessages []*relayerTypes.WarpMessageInfo, + sourceBlockchain config.SourceBlockchain, +) error { + // Send any messages that were specified in the configuration + for _, warpMessage := range manualWarpMessages { + logger.Info( + "Relaying manual Warp message", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), ) + appRelayer, handler, err := lstnr.GetAppRelayerMessageHandler(warpMessage) + if err != nil { + logger.Error( + "Failed to parse manual Warp message.", + zap.Error(err), + zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), + ) + return err + } + err = appRelayer.ProcessMessage(handler) + if err != nil { + logger.Error( + "Failed to process manual Warp message", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), + ) + return err + } } - - // Increment the request ID for the next message relay request - lstnr.currentRequestID++ - return err + return nil } diff --git a/scripts/abi_bindings.sh b/scripts/abi_bindings.sh new file mode 100755 index 00000000..2e91d3e1 --- /dev/null +++ b/scripts/abi_bindings.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash +# Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +# See the file LICENSE for licensing terms. + +set -e + +AWM_RELAYER_PATH=$( + cd "$(dirname "${BASH_SOURCE[0]}")" + cd .. && pwd +) + +source $AWM_RELAYER_PATH/scripts/constants.sh +source $AWM_RELAYER_PATH/scripts/versions.sh +source $TELEPORTER_PATH/scripts/utils.sh + +setARCH + +# Contract names to generate Go bindings for +DEFAULT_CONTRACT_LIST="BatchCrossChainMessenger" + +CONTRACT_LIST= +HELP= +while [ $# -gt 0 ]; do + case "$1" in + -c | --contract) CONTRACT_LIST=$2 ;; + -h | --help) HELP=true ;; + esac + shift +done + +if [ "$HELP" = true ]; then + echo "Usage: ./scripts/abi_bindings.sh [OPTIONS]" + echo "Build contracts and generate Go bindings" + echo "" + echo "Options:" + echo " -c, --contract Generate Go bindings for the contract. If empty, generate Go bindings for a default list of contracts" + echo " -c, --contract "contract1 contract2" Generate Go bindings for multiple contracts" + echo " -h, --help Print this help message" + exit 0 +fi + +if ! command -v forge &> /dev/null; then + echo "forge not found." && exit 1 +fi + +echo "Building subnet-evm abigen" +go install github.com/ava-labs/subnet-evm/cmd/abigen@${SUBNET_EVM_VERSION} + +# Force recompile of all contracts to prevent against using previous +# compilations that did not generate new ABI files. +echo "Building Contracts" +cd $AWM_RELAYER_PATH/tests/contracts +forge build --force --extra-output-files abi bin + +contract_names=($CONTRACT_LIST) + +# If CONTRACT_LIST is empty, use DEFAULT_CONTRACT_LIST +if [[ -z "${CONTRACT_LIST}" ]]; then + contract_names=($DEFAULT_CONTRACT_LIST) +fi + +cd $AWM_RELAYER_PATH/tests/contracts/src +for contract_name in "${contract_names[@]}" +do + path=$(find . -name $contract_name.sol) + dir=$(dirname $path) + abi_file=$AWM_RELAYER_PATH/tests/contracts/out/$contract_name.sol/$contract_name.abi.json + if ! [ -f $abi_file ]; then + echo "Error: Contract $contract_name abi file not found" + exit 1 + fi + + echo "Generating Go bindings for $contract_name..." + gen_path=$AWM_RELAYER_PATH/tests/abi-bindings/go/$dir/$contract_name + mkdir -p $gen_path + $GOPATH/bin/abigen --abi $abi_file \ + --pkg $(convertToLower $contract_name) \ + --bin $AWM_RELAYER_PATH/tests/contracts/out/$contract_name.sol/$contract_name.bin \ + --type $contract_name \ + --out $gen_path/$contract_name.go + echo "Done generating Go bindings for $contract_name." +done + +exit 0 diff --git a/scripts/constants.sh b/scripts/constants.sh index bfb90688..adf237a1 100755 --- a/scripts/constants.sh +++ b/scripts/constants.sh @@ -16,6 +16,9 @@ relayer_path="$RELAYER_PATH/build/awm-relayer" # Set the PATHS GOPATH="$(go env GOPATH)" +TELEPORTER_PATH="$RELAYER_PATH"/tests/contracts/lib/teleporter +source $TELEPORTER_PATH/scripts/constants.sh + # Avalabs docker hub repo is avaplatform/awm-relayer. # Here we default to the local image (awm-relayer) as to avoid unintentional pushes # You should probably set it - export DOCKER_REPO='avaplatform/awm-relayer' diff --git a/tests/abi-bindings/go/BatchCrossChainMessenger/BatchCrossChainMessenger.go b/tests/abi-bindings/go/BatchCrossChainMessenger/BatchCrossChainMessenger.go new file mode 100644 index 00000000..2d28a91a --- /dev/null +++ b/tests/abi-bindings/go/BatchCrossChainMessenger/BatchCrossChainMessenger.go @@ -0,0 +1,1410 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package batchcrosschainmessenger + +import ( + "errors" + "math/big" + "strings" + + "github.com/ava-labs/subnet-evm/accounts/abi" + "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/interfaces" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = interfaces.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// BatchCrossChainMessengerMetaData contains all meta data concerning the BatchCrossChainMessenger contract. +var BatchCrossChainMessengerMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teleporterRegistryAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"teleporterManager\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"oldMinTeleporterVersion\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"newMinTeleporterVersion\",\"type\":\"uint256\"}],\"name\":\"MinTeleporterVersionUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"sourceBlockchainID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"originSenderAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"message\",\"type\":\"string\"}],\"name\":\"ReceiveMessage\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"destinationBlockchainID\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"destinationAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"feeTokenAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"feeAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"requiredGasLimit\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string[]\",\"name\":\"messages\",\"type\":\"string[]\"}],\"name\":\"SendMessages\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"teleporterAddress\",\"type\":\"address\"}],\"name\":\"TeleporterAddressPaused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"teleporterAddress\",\"type\":\"address\"}],\"name\":\"TeleporterAddressUnpaused\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"sourceBlockchainID\",\"type\":\"bytes32\"}],\"name\":\"getCurrentMessages\",\"outputs\":[{\"internalType\":\"string[]\",\"name\":\"\",\"type\":\"string[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getMinTeleporterVersion\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teleporterAddress\",\"type\":\"address\"}],\"name\":\"isTeleporterAddressPaused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teleporterAddress\",\"type\":\"address\"}],\"name\":\"pauseTeleporterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"sourceBlockchainID\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"originSenderAddress\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\"}],\"name\":\"receiveTeleporterMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"destinationBlockchainID\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"destinationAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"feeTokenAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"feeAmount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"requiredGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"string[]\",\"name\":\"messages\",\"type\":\"string[]\"}],\"name\":\"sendMessages\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"teleporterRegistry\",\"outputs\":[{\"internalType\":\"contractTeleporterRegistry\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"teleporterAddress\",\"type\":\"address\"}],\"name\":\"unpauseTeleporterAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"}],\"name\":\"updateMinTeleporterVersion\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x60a06040523480156200001157600080fd5b5060405162001e7938038062001e7983398101604081905262000034916200029f565b60016000558181816001600160a01b038116620000be5760405162461bcd60e51b815260206004820152603760248201527f54656c65706f727465725570677261646561626c653a207a65726f2074656c6560448201527f706f72746572207265676973747279206164647265737300000000000000000060648201526084015b60405180910390fd5b6001600160a01b03811660808190526040805163301fd1f560e21b8152905163c07f47d4916004808201926020929091908290030181865afa15801562000109573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200012f9190620002d7565b600255506200013e3362000153565b6200014981620001a5565b50505050620002f1565b600380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b620001af62000224565b6001600160a01b038116620002165760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401620000b5565b620002218162000153565b50565b6003546001600160a01b03163314620002805760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401620000b5565b565b80516001600160a01b03811681146200029a57600080fd5b919050565b60008060408385031215620002b357600080fd5b620002be8362000282565b9150620002ce6020840162000282565b90509250929050565b600060208284031215620002ea57600080fd5b5051919050565b608051611b58620003216000396000818160be0152818161070401528181610c240152610f660152611b586000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c80638da5cb5b116100715780638da5cb5b146101605780639731429714610171578063c1329fcb146101ad578063c868efaa146101cd578063d2cc7a70146101e0578063f2fde38b146101f157600080fd5b80631a7f5bec146100b95780632b0d8f18146100fd5780633902970c146101125780634511243e146101325780635eb9951414610145578063715018a614610158575b600080fd5b6100e07f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b61011061010b3660046113a3565b610204565b005b61012561012036600461142f565b610309565b6040516100f49190611596565b6101106101403660046113a3565b6104e7565b6101106101533660046115da565b6105e4565b6101106105f8565b6003546001600160a01b03166100e0565b61019d61017f3660046113a3565b6001600160a01b031660009081526001602052604090205460ff1690565b60405190151581526020016100f4565b6101c06101bb3660046115da565b61060c565b6040516100f49190611698565b6101106101db3660046116ab565b6106ef565b6002546040519081526020016100f4565b6101106101ff3660046113a3565b6108b9565b61020c61092f565b6001600160a01b03811661023b5760405162461bcd60e51b815260040161023290611734565b60405180910390fd5b6001600160a01b03811660009081526001602052604090205460ff16156102ba5760405162461bcd60e51b815260206004820152602d60248201527f54656c65706f727465725570677261646561626c653a2061646472657373206160448201526c1b1c9958591e481c185d5cd959609a1b6064820152608401610232565b6001600160a01b0381166000818152600160208190526040808320805460ff1916909217909155517f933f93e57a222e6330362af8b376d0a8725b6901e9a2fb86d00f169702b28a4c9190a250565b6060610313610937565b60008415610328576103258686610990565b90505b866001600160a01b0316887f430d1906813fdb2129a19139f4112a1396804605501a798df3a4042590ba20d5888488886040516103689493929190611782565b60405180910390a36000835167ffffffffffffffff81111561038c5761038c6113c0565b6040519080825280602002602001820160405280156103b5578160200160208202803683370190505b50905060005b84518110156104cf57600061049c6040518060c001604052808d81526020018c6001600160a01b0316815260200160405180604001604052808d6001600160a01b03168152602001888152508152602001898152602001600067ffffffffffffffff81111561042c5761042c6113c0565b604051908082528060200260200182016040528015610455578160200160208202803683370190505b50815260200188858151811061046d5761046d6117af565b602002602001015160405160200161048591906117c5565b604051602081830303815290604052815250610afa565b9050808383815181106104b1576104b16117af565b602090810291909101015250806104c7816117ee565b9150506103bb565b509150506104dd6001600055565b9695505050505050565b6104ef61092f565b6001600160a01b0381166105155760405162461bcd60e51b815260040161023290611734565b6001600160a01b03811660009081526001602052604090205460ff1661058f5760405162461bcd60e51b815260206004820152602960248201527f54656c65706f727465725570677261646561626c653a2061646472657373206e6044820152681bdd081c185d5cd95960ba1b6064820152608401610232565b6040516001600160a01b038216907f844e2f3154214672229235858fd029d1dfd543901c6d05931f0bc2480a2d72c390600090a26001600160a01b03166000908152600160205260409020805460ff19169055565b6105ec61092f565b6105f581610c20565b50565b610600610dc0565b61060a6000610e1a565b565b6000818152600460209081526040808320805482518185028101850190935280835260609493849084015b828210156106e357838290600052602060002001805461065690611807565b80601f016020809104026020016040519081016040528092919081815260200182805461068290611807565b80156106cf5780601f106106a4576101008083540402835291602001916106cf565b820191906000526020600020905b8154815290600101906020018083116106b257829003601f168201915b505050505081526020019060010190610637565b50929695505050505050565b6106f7610937565b6002546001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016634c1f08ce336040516001600160e01b031960e084901b1681526001600160a01b039091166004820152602401602060405180830381865afa15801561076e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107929190611841565b10156107f95760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a20696e76616c6964205460448201526f32b632b837b93a32b91039b2b73232b960811b6064820152608401610232565b6108023361017f565b156108685760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a2054656c65706f72746560448201526f1c881859191c995cdcc81c185d5cd95960821b6064820152608401610232565b6108a9848484848080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610e6c92505050565b6108b36001600055565b50505050565b6108c1610dc0565b6001600160a01b0381166109265760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610232565b6105f581610e1a565b61060a610dc0565b6002600054036109895760405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606401610232565b6002600055565b6040516370a0823160e01b815230600482015260009081906001600160a01b038516906370a0823190602401602060405180830381865afa1580156109d9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109fd9190611841565b9050610a146001600160a01b038516333086610ef6565b6040516370a0823160e01b81523060048201526000906001600160a01b038616906370a0823190602401602060405180830381865afa158015610a5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7f9190611841565b9050818111610ae55760405162461bcd60e51b815260206004820152602c60248201527f5361666545524332305472616e7366657246726f6d3a2062616c616e6365206e60448201526b1bdd081a5b98dc99585cd95960a21b6064820152608401610232565b610aef828261185a565b925050505b92915050565b600080610b05610f61565b60408401516020015190915015610baa576040830151516001600160a01b0316610b875760405162461bcd60e51b815260206004820152602d60248201527f54656c65706f727465725570677261646561626c653a207a65726f206665652060448201526c746f6b656e206164647265737360981b6064820152608401610232565b604083015160208101519051610baa916001600160a01b03909116908390611075565b604051630624488560e41b81526001600160a01b03821690636244885090610bd69086906004016118b1565b6020604051808303816000875af1158015610bf5573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c199190611841565b9392505050565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663c07f47d46040518163ffffffff1660e01b8152600401602060405180830381865afa158015610c80573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ca49190611841565b60025490915081831115610d145760405162461bcd60e51b815260206004820152603160248201527f54656c65706f727465725570677261646561626c653a20696e76616c6964205460448201527032b632b837b93a32b9103b32b939b4b7b760791b6064820152608401610232565b808311610d895760405162461bcd60e51b815260206004820152603f60248201527f54656c65706f727465725570677261646561626c653a206e6f7420677265617460448201527f6572207468616e2063757272656e74206d696e696d756d2076657273696f6e006064820152608401610232565b6002839055604051839082907fa9a7ef57e41f05b4c15480842f5f0c27edfcbb553fed281f7c4068452cc1c02d90600090a3505050565b6003546001600160a01b0316331461060a5760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610232565b600380546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b600081806020019051810190610e82919061192f565b600085815260046020908152604082208054600181018255908352912091925001610ead82826119f4565b50826001600160a01b0316847f1f5c800b5f2b573929a7948f82a199c2a212851b53a6c5bd703ece23999d24aa83604051610ee891906117c5565b60405180910390a350505050565b6040516001600160a01b03808516602483015283166044820152606481018290526108b39085906323b872dd60e01b906084015b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611127565b6000807f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d820e64f6040518163ffffffff1660e01b8152600401602060405180830381865afa158015610fc2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fe69190611ab4565b905061100a816001600160a01b031660009081526001602052604090205460ff1690565b156110705760405162461bcd60e51b815260206004820152603060248201527f54656c65706f727465725570677261646561626c653a2054656c65706f72746560448201526f1c881cd95b991a5b99c81c185d5cd95960821b6064820152608401610232565b919050565b604051636eb1769f60e11b81523060048201526001600160a01b038381166024830152600091839186169063dd62ed3e90604401602060405180830381865afa1580156110c6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110ea9190611841565b6110f49190611ad1565b6040516001600160a01b0385166024820152604481018290529091506108b390859063095ea7b360e01b90606401610f2a565b600061117c826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166111fe9092919063ffffffff16565b8051909150156111f9578080602001905181019061119a9190611ae4565b6111f95760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610232565b505050565b606061120d8484600085611215565b949350505050565b6060824710156112765760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6044820152651c8818d85b1b60d21b6064820152608401610232565b600080866001600160a01b031685876040516112929190611b06565b60006040518083038185875af1925050503d80600081146112cf576040519150601f19603f3d011682016040523d82523d6000602084013e6112d4565b606091505b50915091506112e5878383876112f0565b979650505050505050565b6060831561135f578251600003611358576001600160a01b0385163b6113585760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610232565b508161120d565b61120d83838151156113745781518083602001fd5b8060405162461bcd60e51b815260040161023291906117c5565b6001600160a01b03811681146105f557600080fd5b6000602082840312156113b557600080fd5b8135610c198161138e565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f1916810167ffffffffffffffff811182821017156113ff576113ff6113c0565b604052919050565b600067ffffffffffffffff821115611421576114216113c0565b50601f01601f191660200190565b60008060008060008060c0878903121561144857600080fd5b86359550611459602088013561138e565b6020870135945061146d604088013561138e565b60408701359350606087013592506080870135915067ffffffffffffffff60a0880135111561149b57600080fd5b60a0870135870188601f8201126114b157600080fd5b67ffffffffffffffff813511156114ca576114ca6113c0565b6114da6020823560051b016113d6565b81358082526020808301929160051b8401018b8111156114f957600080fd5b602084015b818110156115845767ffffffffffffffff8135111561151c57600080fd5b803585018d603f82011261152f57600080fd5b602081013561154561154082611407565b6113d6565b8181528f604083850101111561155a57600080fd5b816040840160208301376000602083830101528087525050506020840193506020810190506114fe565b50508093505050509295509295509295565b6020808252825182820181905260009190848201906040850190845b818110156115ce578351835292840192918401916001016115b2565b50909695505050505050565b6000602082840312156115ec57600080fd5b5035919050565b60005b8381101561160e5781810151838201526020016115f6565b50506000910152565b6000815180845261162f8160208601602086016115f3565b601f01601f19169290920160200192915050565b600081518084526020808501808196508360051b8101915082860160005b8581101561168b578284038952611679848351611617565b98850198935090840190600101611661565b5091979650505050505050565b602081526000610c196020830184611643565b600080600080606085870312156116c157600080fd5b8435935060208501356116d38161138e565b9250604085013567ffffffffffffffff808211156116f057600080fd5b818701915087601f83011261170457600080fd5b81358181111561171357600080fd5b88602082850101111561172557600080fd5b95989497505060200194505050565b6020808252602e908201527f54656c65706f727465725570677261646561626c653a207a65726f2054656c6560408201526d706f72746572206164647265737360901b606082015260800190565b60018060a01b03851681528360208201528260408201526080606082015260006104dd6080830184611643565b634e487b7160e01b600052603260045260246000fd5b602081526000610c196020830184611617565b634e487b7160e01b600052601160045260246000fd5b600060018201611800576118006117d8565b5060010190565b600181811c9082168061181b57607f821691505b60208210810361183b57634e487b7160e01b600052602260045260246000fd5b50919050565b60006020828403121561185357600080fd5b5051919050565b81810381811115610af457610af46117d8565b600081518084526020808501945080840160005b838110156118a65781516001600160a01b031687529582019590820190600101611881565b509495945050505050565b60208152815160208201526000602083015160018060a01b03808216604085015260408501519150808251166060850152506020810151608084015250606083015160a0830152608083015160e060c084015261191261010084018261186d565b905060a0840151601f198483030160e0850152610aef8282611617565b60006020828403121561194157600080fd5b815167ffffffffffffffff81111561195857600080fd5b8201601f8101841361196957600080fd5b805161197761154082611407565b81815285602083850101111561198c57600080fd5b61199d8260208301602086016115f3565b95945050505050565b601f8211156111f957600081815260208120601f850160051c810160208610156119cd5750805b601f850160051c820191505b818110156119ec578281556001016119d9565b505050505050565b815167ffffffffffffffff811115611a0e57611a0e6113c0565b611a2281611a1c8454611807565b846119a6565b602080601f831160018114611a575760008415611a3f5750858301515b600019600386901b1c1916600185901b1785556119ec565b600085815260208120601f198616915b82811015611a8657888601518255948401946001909101908401611a67565b5085821015611aa45787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600060208284031215611ac657600080fd5b8151610c198161138e565b80820180821115610af457610af46117d8565b600060208284031215611af657600080fd5b81518015158114610c1957600080fd5b60008251611b188184602087016115f3565b919091019291505056fea2646970667358221220257a55014d2e2efb8773812d91e07b56e09162796771c80a372b9e2533ff794f64736f6c63430008120033", +} + +// BatchCrossChainMessengerABI is the input ABI used to generate the binding from. +// Deprecated: Use BatchCrossChainMessengerMetaData.ABI instead. +var BatchCrossChainMessengerABI = BatchCrossChainMessengerMetaData.ABI + +// BatchCrossChainMessengerBin is the compiled bytecode used for deploying new contracts. +// Deprecated: Use BatchCrossChainMessengerMetaData.Bin instead. +var BatchCrossChainMessengerBin = BatchCrossChainMessengerMetaData.Bin + +// DeployBatchCrossChainMessenger deploys a new Ethereum contract, binding an instance of BatchCrossChainMessenger to it. +func DeployBatchCrossChainMessenger(auth *bind.TransactOpts, backend bind.ContractBackend, teleporterRegistryAddress common.Address, teleporterManager common.Address) (common.Address, *types.Transaction, *BatchCrossChainMessenger, error) { + parsed, err := BatchCrossChainMessengerMetaData.GetAbi() + if err != nil { + return common.Address{}, nil, nil, err + } + if parsed == nil { + return common.Address{}, nil, nil, errors.New("GetABI returned nil") + } + + address, tx, contract, err := bind.DeployContract(auth, *parsed, common.FromHex(BatchCrossChainMessengerBin), backend, teleporterRegistryAddress, teleporterManager) + if err != nil { + return common.Address{}, nil, nil, err + } + return address, tx, &BatchCrossChainMessenger{BatchCrossChainMessengerCaller: BatchCrossChainMessengerCaller{contract: contract}, BatchCrossChainMessengerTransactor: BatchCrossChainMessengerTransactor{contract: contract}, BatchCrossChainMessengerFilterer: BatchCrossChainMessengerFilterer{contract: contract}}, nil +} + +// BatchCrossChainMessenger is an auto generated Go binding around an Ethereum contract. +type BatchCrossChainMessenger struct { + BatchCrossChainMessengerCaller // Read-only binding to the contract + BatchCrossChainMessengerTransactor // Write-only binding to the contract + BatchCrossChainMessengerFilterer // Log filterer for contract events +} + +// BatchCrossChainMessengerCaller is an auto generated read-only Go binding around an Ethereum contract. +type BatchCrossChainMessengerCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BatchCrossChainMessengerTransactor is an auto generated write-only Go binding around an Ethereum contract. +type BatchCrossChainMessengerTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BatchCrossChainMessengerFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type BatchCrossChainMessengerFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// BatchCrossChainMessengerSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type BatchCrossChainMessengerSession struct { + Contract *BatchCrossChainMessenger // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// BatchCrossChainMessengerCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type BatchCrossChainMessengerCallerSession struct { + Contract *BatchCrossChainMessengerCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// BatchCrossChainMessengerTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type BatchCrossChainMessengerTransactorSession struct { + Contract *BatchCrossChainMessengerTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// BatchCrossChainMessengerRaw is an auto generated low-level Go binding around an Ethereum contract. +type BatchCrossChainMessengerRaw struct { + Contract *BatchCrossChainMessenger // Generic contract binding to access the raw methods on +} + +// BatchCrossChainMessengerCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type BatchCrossChainMessengerCallerRaw struct { + Contract *BatchCrossChainMessengerCaller // Generic read-only contract binding to access the raw methods on +} + +// BatchCrossChainMessengerTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type BatchCrossChainMessengerTransactorRaw struct { + Contract *BatchCrossChainMessengerTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewBatchCrossChainMessenger creates a new instance of BatchCrossChainMessenger, bound to a specific deployed contract. +func NewBatchCrossChainMessenger(address common.Address, backend bind.ContractBackend) (*BatchCrossChainMessenger, error) { + contract, err := bindBatchCrossChainMessenger(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &BatchCrossChainMessenger{BatchCrossChainMessengerCaller: BatchCrossChainMessengerCaller{contract: contract}, BatchCrossChainMessengerTransactor: BatchCrossChainMessengerTransactor{contract: contract}, BatchCrossChainMessengerFilterer: BatchCrossChainMessengerFilterer{contract: contract}}, nil +} + +// NewBatchCrossChainMessengerCaller creates a new read-only instance of BatchCrossChainMessenger, bound to a specific deployed contract. +func NewBatchCrossChainMessengerCaller(address common.Address, caller bind.ContractCaller) (*BatchCrossChainMessengerCaller, error) { + contract, err := bindBatchCrossChainMessenger(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerCaller{contract: contract}, nil +} + +// NewBatchCrossChainMessengerTransactor creates a new write-only instance of BatchCrossChainMessenger, bound to a specific deployed contract. +func NewBatchCrossChainMessengerTransactor(address common.Address, transactor bind.ContractTransactor) (*BatchCrossChainMessengerTransactor, error) { + contract, err := bindBatchCrossChainMessenger(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerTransactor{contract: contract}, nil +} + +// NewBatchCrossChainMessengerFilterer creates a new log filterer instance of BatchCrossChainMessenger, bound to a specific deployed contract. +func NewBatchCrossChainMessengerFilterer(address common.Address, filterer bind.ContractFilterer) (*BatchCrossChainMessengerFilterer, error) { + contract, err := bindBatchCrossChainMessenger(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerFilterer{contract: contract}, nil +} + +// bindBatchCrossChainMessenger binds a generic wrapper to an already deployed contract. +func bindBatchCrossChainMessenger(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := BatchCrossChainMessengerMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_BatchCrossChainMessenger *BatchCrossChainMessengerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _BatchCrossChainMessenger.Contract.BatchCrossChainMessengerCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_BatchCrossChainMessenger *BatchCrossChainMessengerRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.BatchCrossChainMessengerTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_BatchCrossChainMessenger *BatchCrossChainMessengerRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.BatchCrossChainMessengerTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _BatchCrossChainMessenger.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.contract.Transact(opts, method, params...) +} + +// GetCurrentMessages is a free data retrieval call binding the contract method 0xc1329fcb. +// +// Solidity: function getCurrentMessages(bytes32 sourceBlockchainID) view returns(string[]) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCaller) GetCurrentMessages(opts *bind.CallOpts, sourceBlockchainID [32]byte) ([]string, error) { + var out []interface{} + err := _BatchCrossChainMessenger.contract.Call(opts, &out, "getCurrentMessages", sourceBlockchainID) + + if err != nil { + return *new([]string), err + } + + out0 := *abi.ConvertType(out[0], new([]string)).(*[]string) + + return out0, err + +} + +// GetCurrentMessages is a free data retrieval call binding the contract method 0xc1329fcb. +// +// Solidity: function getCurrentMessages(bytes32 sourceBlockchainID) view returns(string[]) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) GetCurrentMessages(sourceBlockchainID [32]byte) ([]string, error) { + return _BatchCrossChainMessenger.Contract.GetCurrentMessages(&_BatchCrossChainMessenger.CallOpts, sourceBlockchainID) +} + +// GetCurrentMessages is a free data retrieval call binding the contract method 0xc1329fcb. +// +// Solidity: function getCurrentMessages(bytes32 sourceBlockchainID) view returns(string[]) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCallerSession) GetCurrentMessages(sourceBlockchainID [32]byte) ([]string, error) { + return _BatchCrossChainMessenger.Contract.GetCurrentMessages(&_BatchCrossChainMessenger.CallOpts, sourceBlockchainID) +} + +// GetMinTeleporterVersion is a free data retrieval call binding the contract method 0xd2cc7a70. +// +// Solidity: function getMinTeleporterVersion() view returns(uint256) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCaller) GetMinTeleporterVersion(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _BatchCrossChainMessenger.contract.Call(opts, &out, "getMinTeleporterVersion") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetMinTeleporterVersion is a free data retrieval call binding the contract method 0xd2cc7a70. +// +// Solidity: function getMinTeleporterVersion() view returns(uint256) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) GetMinTeleporterVersion() (*big.Int, error) { + return _BatchCrossChainMessenger.Contract.GetMinTeleporterVersion(&_BatchCrossChainMessenger.CallOpts) +} + +// GetMinTeleporterVersion is a free data retrieval call binding the contract method 0xd2cc7a70. +// +// Solidity: function getMinTeleporterVersion() view returns(uint256) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCallerSession) GetMinTeleporterVersion() (*big.Int, error) { + return _BatchCrossChainMessenger.Contract.GetMinTeleporterVersion(&_BatchCrossChainMessenger.CallOpts) +} + +// IsTeleporterAddressPaused is a free data retrieval call binding the contract method 0x97314297. +// +// Solidity: function isTeleporterAddressPaused(address teleporterAddress) view returns(bool) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCaller) IsTeleporterAddressPaused(opts *bind.CallOpts, teleporterAddress common.Address) (bool, error) { + var out []interface{} + err := _BatchCrossChainMessenger.contract.Call(opts, &out, "isTeleporterAddressPaused", teleporterAddress) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsTeleporterAddressPaused is a free data retrieval call binding the contract method 0x97314297. +// +// Solidity: function isTeleporterAddressPaused(address teleporterAddress) view returns(bool) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) IsTeleporterAddressPaused(teleporterAddress common.Address) (bool, error) { + return _BatchCrossChainMessenger.Contract.IsTeleporterAddressPaused(&_BatchCrossChainMessenger.CallOpts, teleporterAddress) +} + +// IsTeleporterAddressPaused is a free data retrieval call binding the contract method 0x97314297. +// +// Solidity: function isTeleporterAddressPaused(address teleporterAddress) view returns(bool) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCallerSession) IsTeleporterAddressPaused(teleporterAddress common.Address) (bool, error) { + return _BatchCrossChainMessenger.Contract.IsTeleporterAddressPaused(&_BatchCrossChainMessenger.CallOpts, teleporterAddress) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _BatchCrossChainMessenger.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) Owner() (common.Address, error) { + return _BatchCrossChainMessenger.Contract.Owner(&_BatchCrossChainMessenger.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCallerSession) Owner() (common.Address, error) { + return _BatchCrossChainMessenger.Contract.Owner(&_BatchCrossChainMessenger.CallOpts) +} + +// TeleporterRegistry is a free data retrieval call binding the contract method 0x1a7f5bec. +// +// Solidity: function teleporterRegistry() view returns(address) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCaller) TeleporterRegistry(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _BatchCrossChainMessenger.contract.Call(opts, &out, "teleporterRegistry") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// TeleporterRegistry is a free data retrieval call binding the contract method 0x1a7f5bec. +// +// Solidity: function teleporterRegistry() view returns(address) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) TeleporterRegistry() (common.Address, error) { + return _BatchCrossChainMessenger.Contract.TeleporterRegistry(&_BatchCrossChainMessenger.CallOpts) +} + +// TeleporterRegistry is a free data retrieval call binding the contract method 0x1a7f5bec. +// +// Solidity: function teleporterRegistry() view returns(address) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerCallerSession) TeleporterRegistry() (common.Address, error) { + return _BatchCrossChainMessenger.Contract.TeleporterRegistry(&_BatchCrossChainMessenger.CallOpts) +} + +// PauseTeleporterAddress is a paid mutator transaction binding the contract method 0x2b0d8f18. +// +// Solidity: function pauseTeleporterAddress(address teleporterAddress) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactor) PauseTeleporterAddress(opts *bind.TransactOpts, teleporterAddress common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.contract.Transact(opts, "pauseTeleporterAddress", teleporterAddress) +} + +// PauseTeleporterAddress is a paid mutator transaction binding the contract method 0x2b0d8f18. +// +// Solidity: function pauseTeleporterAddress(address teleporterAddress) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) PauseTeleporterAddress(teleporterAddress common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.PauseTeleporterAddress(&_BatchCrossChainMessenger.TransactOpts, teleporterAddress) +} + +// PauseTeleporterAddress is a paid mutator transaction binding the contract method 0x2b0d8f18. +// +// Solidity: function pauseTeleporterAddress(address teleporterAddress) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorSession) PauseTeleporterAddress(teleporterAddress common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.PauseTeleporterAddress(&_BatchCrossChainMessenger.TransactOpts, teleporterAddress) +} + +// ReceiveTeleporterMessage is a paid mutator transaction binding the contract method 0xc868efaa. +// +// Solidity: function receiveTeleporterMessage(bytes32 sourceBlockchainID, address originSenderAddress, bytes message) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactor) ReceiveTeleporterMessage(opts *bind.TransactOpts, sourceBlockchainID [32]byte, originSenderAddress common.Address, message []byte) (*types.Transaction, error) { + return _BatchCrossChainMessenger.contract.Transact(opts, "receiveTeleporterMessage", sourceBlockchainID, originSenderAddress, message) +} + +// ReceiveTeleporterMessage is a paid mutator transaction binding the contract method 0xc868efaa. +// +// Solidity: function receiveTeleporterMessage(bytes32 sourceBlockchainID, address originSenderAddress, bytes message) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) ReceiveTeleporterMessage(sourceBlockchainID [32]byte, originSenderAddress common.Address, message []byte) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.ReceiveTeleporterMessage(&_BatchCrossChainMessenger.TransactOpts, sourceBlockchainID, originSenderAddress, message) +} + +// ReceiveTeleporterMessage is a paid mutator transaction binding the contract method 0xc868efaa. +// +// Solidity: function receiveTeleporterMessage(bytes32 sourceBlockchainID, address originSenderAddress, bytes message) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorSession) ReceiveTeleporterMessage(sourceBlockchainID [32]byte, originSenderAddress common.Address, message []byte) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.ReceiveTeleporterMessage(&_BatchCrossChainMessenger.TransactOpts, sourceBlockchainID, originSenderAddress, message) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _BatchCrossChainMessenger.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) RenounceOwnership() (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.RenounceOwnership(&_BatchCrossChainMessenger.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.RenounceOwnership(&_BatchCrossChainMessenger.TransactOpts) +} + +// SendMessages is a paid mutator transaction binding the contract method 0x3902970c. +// +// Solidity: function sendMessages(bytes32 destinationBlockchainID, address destinationAddress, address feeTokenAddress, uint256 feeAmount, uint256 requiredGasLimit, string[] messages) returns(bytes32[]) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactor) SendMessages(opts *bind.TransactOpts, destinationBlockchainID [32]byte, destinationAddress common.Address, feeTokenAddress common.Address, feeAmount *big.Int, requiredGasLimit *big.Int, messages []string) (*types.Transaction, error) { + return _BatchCrossChainMessenger.contract.Transact(opts, "sendMessages", destinationBlockchainID, destinationAddress, feeTokenAddress, feeAmount, requiredGasLimit, messages) +} + +// SendMessages is a paid mutator transaction binding the contract method 0x3902970c. +// +// Solidity: function sendMessages(bytes32 destinationBlockchainID, address destinationAddress, address feeTokenAddress, uint256 feeAmount, uint256 requiredGasLimit, string[] messages) returns(bytes32[]) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) SendMessages(destinationBlockchainID [32]byte, destinationAddress common.Address, feeTokenAddress common.Address, feeAmount *big.Int, requiredGasLimit *big.Int, messages []string) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.SendMessages(&_BatchCrossChainMessenger.TransactOpts, destinationBlockchainID, destinationAddress, feeTokenAddress, feeAmount, requiredGasLimit, messages) +} + +// SendMessages is a paid mutator transaction binding the contract method 0x3902970c. +// +// Solidity: function sendMessages(bytes32 destinationBlockchainID, address destinationAddress, address feeTokenAddress, uint256 feeAmount, uint256 requiredGasLimit, string[] messages) returns(bytes32[]) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorSession) SendMessages(destinationBlockchainID [32]byte, destinationAddress common.Address, feeTokenAddress common.Address, feeAmount *big.Int, requiredGasLimit *big.Int, messages []string) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.SendMessages(&_BatchCrossChainMessenger.TransactOpts, destinationBlockchainID, destinationAddress, feeTokenAddress, feeAmount, requiredGasLimit, messages) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.TransferOwnership(&_BatchCrossChainMessenger.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.TransferOwnership(&_BatchCrossChainMessenger.TransactOpts, newOwner) +} + +// UnpauseTeleporterAddress is a paid mutator transaction binding the contract method 0x4511243e. +// +// Solidity: function unpauseTeleporterAddress(address teleporterAddress) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactor) UnpauseTeleporterAddress(opts *bind.TransactOpts, teleporterAddress common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.contract.Transact(opts, "unpauseTeleporterAddress", teleporterAddress) +} + +// UnpauseTeleporterAddress is a paid mutator transaction binding the contract method 0x4511243e. +// +// Solidity: function unpauseTeleporterAddress(address teleporterAddress) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) UnpauseTeleporterAddress(teleporterAddress common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.UnpauseTeleporterAddress(&_BatchCrossChainMessenger.TransactOpts, teleporterAddress) +} + +// UnpauseTeleporterAddress is a paid mutator transaction binding the contract method 0x4511243e. +// +// Solidity: function unpauseTeleporterAddress(address teleporterAddress) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorSession) UnpauseTeleporterAddress(teleporterAddress common.Address) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.UnpauseTeleporterAddress(&_BatchCrossChainMessenger.TransactOpts, teleporterAddress) +} + +// UpdateMinTeleporterVersion is a paid mutator transaction binding the contract method 0x5eb99514. +// +// Solidity: function updateMinTeleporterVersion(uint256 version) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactor) UpdateMinTeleporterVersion(opts *bind.TransactOpts, version *big.Int) (*types.Transaction, error) { + return _BatchCrossChainMessenger.contract.Transact(opts, "updateMinTeleporterVersion", version) +} + +// UpdateMinTeleporterVersion is a paid mutator transaction binding the contract method 0x5eb99514. +// +// Solidity: function updateMinTeleporterVersion(uint256 version) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerSession) UpdateMinTeleporterVersion(version *big.Int) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.UpdateMinTeleporterVersion(&_BatchCrossChainMessenger.TransactOpts, version) +} + +// UpdateMinTeleporterVersion is a paid mutator transaction binding the contract method 0x5eb99514. +// +// Solidity: function updateMinTeleporterVersion(uint256 version) returns() +func (_BatchCrossChainMessenger *BatchCrossChainMessengerTransactorSession) UpdateMinTeleporterVersion(version *big.Int) (*types.Transaction, error) { + return _BatchCrossChainMessenger.Contract.UpdateMinTeleporterVersion(&_BatchCrossChainMessenger.TransactOpts, version) +} + +// BatchCrossChainMessengerMinTeleporterVersionUpdatedIterator is returned from FilterMinTeleporterVersionUpdated and is used to iterate over the raw logs and unpacked data for MinTeleporterVersionUpdated events raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerMinTeleporterVersionUpdatedIterator struct { + Event *BatchCrossChainMessengerMinTeleporterVersionUpdated // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub interfaces.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BatchCrossChainMessengerMinTeleporterVersionUpdatedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerMinTeleporterVersionUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerMinTeleporterVersionUpdated) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BatchCrossChainMessengerMinTeleporterVersionUpdatedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BatchCrossChainMessengerMinTeleporterVersionUpdatedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BatchCrossChainMessengerMinTeleporterVersionUpdated represents a MinTeleporterVersionUpdated event raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerMinTeleporterVersionUpdated struct { + OldMinTeleporterVersion *big.Int + NewMinTeleporterVersion *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterMinTeleporterVersionUpdated is a free log retrieval operation binding the contract event 0xa9a7ef57e41f05b4c15480842f5f0c27edfcbb553fed281f7c4068452cc1c02d. +// +// Solidity: event MinTeleporterVersionUpdated(uint256 indexed oldMinTeleporterVersion, uint256 indexed newMinTeleporterVersion) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) FilterMinTeleporterVersionUpdated(opts *bind.FilterOpts, oldMinTeleporterVersion []*big.Int, newMinTeleporterVersion []*big.Int) (*BatchCrossChainMessengerMinTeleporterVersionUpdatedIterator, error) { + + var oldMinTeleporterVersionRule []interface{} + for _, oldMinTeleporterVersionItem := range oldMinTeleporterVersion { + oldMinTeleporterVersionRule = append(oldMinTeleporterVersionRule, oldMinTeleporterVersionItem) + } + var newMinTeleporterVersionRule []interface{} + for _, newMinTeleporterVersionItem := range newMinTeleporterVersion { + newMinTeleporterVersionRule = append(newMinTeleporterVersionRule, newMinTeleporterVersionItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.FilterLogs(opts, "MinTeleporterVersionUpdated", oldMinTeleporterVersionRule, newMinTeleporterVersionRule) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerMinTeleporterVersionUpdatedIterator{contract: _BatchCrossChainMessenger.contract, event: "MinTeleporterVersionUpdated", logs: logs, sub: sub}, nil +} + +// WatchMinTeleporterVersionUpdated is a free log subscription operation binding the contract event 0xa9a7ef57e41f05b4c15480842f5f0c27edfcbb553fed281f7c4068452cc1c02d. +// +// Solidity: event MinTeleporterVersionUpdated(uint256 indexed oldMinTeleporterVersion, uint256 indexed newMinTeleporterVersion) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) WatchMinTeleporterVersionUpdated(opts *bind.WatchOpts, sink chan<- *BatchCrossChainMessengerMinTeleporterVersionUpdated, oldMinTeleporterVersion []*big.Int, newMinTeleporterVersion []*big.Int) (event.Subscription, error) { + + var oldMinTeleporterVersionRule []interface{} + for _, oldMinTeleporterVersionItem := range oldMinTeleporterVersion { + oldMinTeleporterVersionRule = append(oldMinTeleporterVersionRule, oldMinTeleporterVersionItem) + } + var newMinTeleporterVersionRule []interface{} + for _, newMinTeleporterVersionItem := range newMinTeleporterVersion { + newMinTeleporterVersionRule = append(newMinTeleporterVersionRule, newMinTeleporterVersionItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.WatchLogs(opts, "MinTeleporterVersionUpdated", oldMinTeleporterVersionRule, newMinTeleporterVersionRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BatchCrossChainMessengerMinTeleporterVersionUpdated) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "MinTeleporterVersionUpdated", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseMinTeleporterVersionUpdated is a log parse operation binding the contract event 0xa9a7ef57e41f05b4c15480842f5f0c27edfcbb553fed281f7c4068452cc1c02d. +// +// Solidity: event MinTeleporterVersionUpdated(uint256 indexed oldMinTeleporterVersion, uint256 indexed newMinTeleporterVersion) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) ParseMinTeleporterVersionUpdated(log types.Log) (*BatchCrossChainMessengerMinTeleporterVersionUpdated, error) { + event := new(BatchCrossChainMessengerMinTeleporterVersionUpdated) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "MinTeleporterVersionUpdated", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BatchCrossChainMessengerOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerOwnershipTransferredIterator struct { + Event *BatchCrossChainMessengerOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub interfaces.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BatchCrossChainMessengerOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BatchCrossChainMessengerOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BatchCrossChainMessengerOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BatchCrossChainMessengerOwnershipTransferred represents a OwnershipTransferred event raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*BatchCrossChainMessengerOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerOwnershipTransferredIterator{contract: _BatchCrossChainMessenger.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *BatchCrossChainMessengerOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BatchCrossChainMessengerOwnershipTransferred) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) ParseOwnershipTransferred(log types.Log) (*BatchCrossChainMessengerOwnershipTransferred, error) { + event := new(BatchCrossChainMessengerOwnershipTransferred) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BatchCrossChainMessengerReceiveMessageIterator is returned from FilterReceiveMessage and is used to iterate over the raw logs and unpacked data for ReceiveMessage events raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerReceiveMessageIterator struct { + Event *BatchCrossChainMessengerReceiveMessage // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub interfaces.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BatchCrossChainMessengerReceiveMessageIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerReceiveMessage) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerReceiveMessage) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BatchCrossChainMessengerReceiveMessageIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BatchCrossChainMessengerReceiveMessageIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BatchCrossChainMessengerReceiveMessage represents a ReceiveMessage event raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerReceiveMessage struct { + SourceBlockchainID [32]byte + OriginSenderAddress common.Address + Message string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterReceiveMessage is a free log retrieval operation binding the contract event 0x1f5c800b5f2b573929a7948f82a199c2a212851b53a6c5bd703ece23999d24aa. +// +// Solidity: event ReceiveMessage(bytes32 indexed sourceBlockchainID, address indexed originSenderAddress, string message) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) FilterReceiveMessage(opts *bind.FilterOpts, sourceBlockchainID [][32]byte, originSenderAddress []common.Address) (*BatchCrossChainMessengerReceiveMessageIterator, error) { + + var sourceBlockchainIDRule []interface{} + for _, sourceBlockchainIDItem := range sourceBlockchainID { + sourceBlockchainIDRule = append(sourceBlockchainIDRule, sourceBlockchainIDItem) + } + var originSenderAddressRule []interface{} + for _, originSenderAddressItem := range originSenderAddress { + originSenderAddressRule = append(originSenderAddressRule, originSenderAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.FilterLogs(opts, "ReceiveMessage", sourceBlockchainIDRule, originSenderAddressRule) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerReceiveMessageIterator{contract: _BatchCrossChainMessenger.contract, event: "ReceiveMessage", logs: logs, sub: sub}, nil +} + +// WatchReceiveMessage is a free log subscription operation binding the contract event 0x1f5c800b5f2b573929a7948f82a199c2a212851b53a6c5bd703ece23999d24aa. +// +// Solidity: event ReceiveMessage(bytes32 indexed sourceBlockchainID, address indexed originSenderAddress, string message) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) WatchReceiveMessage(opts *bind.WatchOpts, sink chan<- *BatchCrossChainMessengerReceiveMessage, sourceBlockchainID [][32]byte, originSenderAddress []common.Address) (event.Subscription, error) { + + var sourceBlockchainIDRule []interface{} + for _, sourceBlockchainIDItem := range sourceBlockchainID { + sourceBlockchainIDRule = append(sourceBlockchainIDRule, sourceBlockchainIDItem) + } + var originSenderAddressRule []interface{} + for _, originSenderAddressItem := range originSenderAddress { + originSenderAddressRule = append(originSenderAddressRule, originSenderAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.WatchLogs(opts, "ReceiveMessage", sourceBlockchainIDRule, originSenderAddressRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BatchCrossChainMessengerReceiveMessage) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "ReceiveMessage", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseReceiveMessage is a log parse operation binding the contract event 0x1f5c800b5f2b573929a7948f82a199c2a212851b53a6c5bd703ece23999d24aa. +// +// Solidity: event ReceiveMessage(bytes32 indexed sourceBlockchainID, address indexed originSenderAddress, string message) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) ParseReceiveMessage(log types.Log) (*BatchCrossChainMessengerReceiveMessage, error) { + event := new(BatchCrossChainMessengerReceiveMessage) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "ReceiveMessage", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BatchCrossChainMessengerSendMessagesIterator is returned from FilterSendMessages and is used to iterate over the raw logs and unpacked data for SendMessages events raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerSendMessagesIterator struct { + Event *BatchCrossChainMessengerSendMessages // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub interfaces.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BatchCrossChainMessengerSendMessagesIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerSendMessages) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerSendMessages) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BatchCrossChainMessengerSendMessagesIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BatchCrossChainMessengerSendMessagesIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BatchCrossChainMessengerSendMessages represents a SendMessages event raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerSendMessages struct { + DestinationBlockchainID [32]byte + DestinationAddress common.Address + FeeTokenAddress common.Address + FeeAmount *big.Int + RequiredGasLimit *big.Int + Messages []string + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSendMessages is a free log retrieval operation binding the contract event 0x430d1906813fdb2129a19139f4112a1396804605501a798df3a4042590ba20d5. +// +// Solidity: event SendMessages(bytes32 indexed destinationBlockchainID, address indexed destinationAddress, address feeTokenAddress, uint256 feeAmount, uint256 requiredGasLimit, string[] messages) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) FilterSendMessages(opts *bind.FilterOpts, destinationBlockchainID [][32]byte, destinationAddress []common.Address) (*BatchCrossChainMessengerSendMessagesIterator, error) { + + var destinationBlockchainIDRule []interface{} + for _, destinationBlockchainIDItem := range destinationBlockchainID { + destinationBlockchainIDRule = append(destinationBlockchainIDRule, destinationBlockchainIDItem) + } + var destinationAddressRule []interface{} + for _, destinationAddressItem := range destinationAddress { + destinationAddressRule = append(destinationAddressRule, destinationAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.FilterLogs(opts, "SendMessages", destinationBlockchainIDRule, destinationAddressRule) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerSendMessagesIterator{contract: _BatchCrossChainMessenger.contract, event: "SendMessages", logs: logs, sub: sub}, nil +} + +// WatchSendMessages is a free log subscription operation binding the contract event 0x430d1906813fdb2129a19139f4112a1396804605501a798df3a4042590ba20d5. +// +// Solidity: event SendMessages(bytes32 indexed destinationBlockchainID, address indexed destinationAddress, address feeTokenAddress, uint256 feeAmount, uint256 requiredGasLimit, string[] messages) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) WatchSendMessages(opts *bind.WatchOpts, sink chan<- *BatchCrossChainMessengerSendMessages, destinationBlockchainID [][32]byte, destinationAddress []common.Address) (event.Subscription, error) { + + var destinationBlockchainIDRule []interface{} + for _, destinationBlockchainIDItem := range destinationBlockchainID { + destinationBlockchainIDRule = append(destinationBlockchainIDRule, destinationBlockchainIDItem) + } + var destinationAddressRule []interface{} + for _, destinationAddressItem := range destinationAddress { + destinationAddressRule = append(destinationAddressRule, destinationAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.WatchLogs(opts, "SendMessages", destinationBlockchainIDRule, destinationAddressRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BatchCrossChainMessengerSendMessages) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "SendMessages", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSendMessages is a log parse operation binding the contract event 0x430d1906813fdb2129a19139f4112a1396804605501a798df3a4042590ba20d5. +// +// Solidity: event SendMessages(bytes32 indexed destinationBlockchainID, address indexed destinationAddress, address feeTokenAddress, uint256 feeAmount, uint256 requiredGasLimit, string[] messages) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) ParseSendMessages(log types.Log) (*BatchCrossChainMessengerSendMessages, error) { + event := new(BatchCrossChainMessengerSendMessages) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "SendMessages", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BatchCrossChainMessengerTeleporterAddressPausedIterator is returned from FilterTeleporterAddressPaused and is used to iterate over the raw logs and unpacked data for TeleporterAddressPaused events raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerTeleporterAddressPausedIterator struct { + Event *BatchCrossChainMessengerTeleporterAddressPaused // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub interfaces.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BatchCrossChainMessengerTeleporterAddressPausedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerTeleporterAddressPaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerTeleporterAddressPaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BatchCrossChainMessengerTeleporterAddressPausedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BatchCrossChainMessengerTeleporterAddressPausedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BatchCrossChainMessengerTeleporterAddressPaused represents a TeleporterAddressPaused event raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerTeleporterAddressPaused struct { + TeleporterAddress common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTeleporterAddressPaused is a free log retrieval operation binding the contract event 0x933f93e57a222e6330362af8b376d0a8725b6901e9a2fb86d00f169702b28a4c. +// +// Solidity: event TeleporterAddressPaused(address indexed teleporterAddress) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) FilterTeleporterAddressPaused(opts *bind.FilterOpts, teleporterAddress []common.Address) (*BatchCrossChainMessengerTeleporterAddressPausedIterator, error) { + + var teleporterAddressRule []interface{} + for _, teleporterAddressItem := range teleporterAddress { + teleporterAddressRule = append(teleporterAddressRule, teleporterAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.FilterLogs(opts, "TeleporterAddressPaused", teleporterAddressRule) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerTeleporterAddressPausedIterator{contract: _BatchCrossChainMessenger.contract, event: "TeleporterAddressPaused", logs: logs, sub: sub}, nil +} + +// WatchTeleporterAddressPaused is a free log subscription operation binding the contract event 0x933f93e57a222e6330362af8b376d0a8725b6901e9a2fb86d00f169702b28a4c. +// +// Solidity: event TeleporterAddressPaused(address indexed teleporterAddress) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) WatchTeleporterAddressPaused(opts *bind.WatchOpts, sink chan<- *BatchCrossChainMessengerTeleporterAddressPaused, teleporterAddress []common.Address) (event.Subscription, error) { + + var teleporterAddressRule []interface{} + for _, teleporterAddressItem := range teleporterAddress { + teleporterAddressRule = append(teleporterAddressRule, teleporterAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.WatchLogs(opts, "TeleporterAddressPaused", teleporterAddressRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BatchCrossChainMessengerTeleporterAddressPaused) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "TeleporterAddressPaused", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseTeleporterAddressPaused is a log parse operation binding the contract event 0x933f93e57a222e6330362af8b376d0a8725b6901e9a2fb86d00f169702b28a4c. +// +// Solidity: event TeleporterAddressPaused(address indexed teleporterAddress) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) ParseTeleporterAddressPaused(log types.Log) (*BatchCrossChainMessengerTeleporterAddressPaused, error) { + event := new(BatchCrossChainMessengerTeleporterAddressPaused) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "TeleporterAddressPaused", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// BatchCrossChainMessengerTeleporterAddressUnpausedIterator is returned from FilterTeleporterAddressUnpaused and is used to iterate over the raw logs and unpacked data for TeleporterAddressUnpaused events raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerTeleporterAddressUnpausedIterator struct { + Event *BatchCrossChainMessengerTeleporterAddressUnpaused // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub interfaces.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *BatchCrossChainMessengerTeleporterAddressUnpausedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerTeleporterAddressUnpaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(BatchCrossChainMessengerTeleporterAddressUnpaused) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *BatchCrossChainMessengerTeleporterAddressUnpausedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *BatchCrossChainMessengerTeleporterAddressUnpausedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// BatchCrossChainMessengerTeleporterAddressUnpaused represents a TeleporterAddressUnpaused event raised by the BatchCrossChainMessenger contract. +type BatchCrossChainMessengerTeleporterAddressUnpaused struct { + TeleporterAddress common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTeleporterAddressUnpaused is a free log retrieval operation binding the contract event 0x844e2f3154214672229235858fd029d1dfd543901c6d05931f0bc2480a2d72c3. +// +// Solidity: event TeleporterAddressUnpaused(address indexed teleporterAddress) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) FilterTeleporterAddressUnpaused(opts *bind.FilterOpts, teleporterAddress []common.Address) (*BatchCrossChainMessengerTeleporterAddressUnpausedIterator, error) { + + var teleporterAddressRule []interface{} + for _, teleporterAddressItem := range teleporterAddress { + teleporterAddressRule = append(teleporterAddressRule, teleporterAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.FilterLogs(opts, "TeleporterAddressUnpaused", teleporterAddressRule) + if err != nil { + return nil, err + } + return &BatchCrossChainMessengerTeleporterAddressUnpausedIterator{contract: _BatchCrossChainMessenger.contract, event: "TeleporterAddressUnpaused", logs: logs, sub: sub}, nil +} + +// WatchTeleporterAddressUnpaused is a free log subscription operation binding the contract event 0x844e2f3154214672229235858fd029d1dfd543901c6d05931f0bc2480a2d72c3. +// +// Solidity: event TeleporterAddressUnpaused(address indexed teleporterAddress) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) WatchTeleporterAddressUnpaused(opts *bind.WatchOpts, sink chan<- *BatchCrossChainMessengerTeleporterAddressUnpaused, teleporterAddress []common.Address) (event.Subscription, error) { + + var teleporterAddressRule []interface{} + for _, teleporterAddressItem := range teleporterAddress { + teleporterAddressRule = append(teleporterAddressRule, teleporterAddressItem) + } + + logs, sub, err := _BatchCrossChainMessenger.contract.WatchLogs(opts, "TeleporterAddressUnpaused", teleporterAddressRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(BatchCrossChainMessengerTeleporterAddressUnpaused) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "TeleporterAddressUnpaused", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseTeleporterAddressUnpaused is a log parse operation binding the contract event 0x844e2f3154214672229235858fd029d1dfd543901c6d05931f0bc2480a2d72c3. +// +// Solidity: event TeleporterAddressUnpaused(address indexed teleporterAddress) +func (_BatchCrossChainMessenger *BatchCrossChainMessengerFilterer) ParseTeleporterAddressUnpaused(log types.Log) (*BatchCrossChainMessengerTeleporterAddressUnpaused, error) { + event := new(BatchCrossChainMessengerTeleporterAddressUnpaused) + if err := _BatchCrossChainMessenger.contract.UnpackLog(event, "TeleporterAddressUnpaused", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/tests/batch_relay.go b/tests/batch_relay.go new file mode 100644 index 00000000..808eb436 --- /dev/null +++ b/tests/batch_relay.go @@ -0,0 +1,126 @@ +package tests + +import ( + "context" + "fmt" + "math/big" + "strconv" + "time" + + "github.com/ava-labs/avalanchego/utils/set" + testUtils "github.com/ava-labs/awm-relayer/tests/utils" + "github.com/ava-labs/subnet-evm/accounts/abi/bind" + "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + . "github.com/onsi/gomega" +) + +// Processes multiple Warp messages contained in the same block +func BatchRelay(network interfaces.LocalNetwork) { + subnetAInfo, subnetBInfo := utils.GetTwoSubnets(network) + fundedAddress, fundedKey := network.GetFundedAccountInfo() + teleporterContractAddress := network.GetTeleporterContractAddress() + err := testUtils.ClearRelayerStorage() + Expect(err).Should(BeNil()) + + // + // Deploy the batch messenger contracts + // + ctx := context.Background() + _, batchMessengerA := testUtils.DeployBatchCrossChainMessenger( + ctx, + fundedKey, + fundedAddress, + subnetAInfo, + ) + batchMessengerAddressB, batchMessengerB := testUtils.DeployBatchCrossChainMessenger( + ctx, + fundedKey, + fundedAddress, + subnetBInfo, + ) + + // + // Fund the relayer address on all subnets + // + + log.Info("Funding relayer address on all subnets") + relayerKey, err := crypto.GenerateKey() + Expect(err).Should(BeNil()) + testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, fundedKey, relayerKey) + + // + // Set up relayer config + // + relayerConfig := testUtils.CreateDefaultRelayerConfig( + []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, + []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, + teleporterContractAddress, + fundedAddress, + relayerKey, + ) + + relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname) + + log.Info("Starting the relayer") + relayerCleanup := testUtils.BuildAndRunRelayerExecutable(ctx, relayerConfigPath) + defer relayerCleanup() + + // Sleep for some time to make sure relayer has started up and subscribed. + log.Info("Waiting for the relayer to start up") + time.Sleep(15 * time.Second) + + // + // Send a batch message from subnet A -> B + // + + newHeadsDest := make(chan *types.Header, 10) + sub, err := subnetBInfo.WSClient.SubscribeNewHead(ctx, newHeadsDest) + Expect(err).Should(BeNil()) + defer sub.Unsubscribe() + + numMessages := 50 + sentMessages := set.NewSet[string](numMessages) + for i := 0; i < numMessages; i++ { + sentMessages.Add(strconv.Itoa(i)) + } + + optsA, err := bind.NewKeyedTransactorWithChainID(fundedKey, subnetAInfo.EVMChainID) + Expect(err).Should(BeNil()) + tx, err := batchMessengerA.SendMessages( + optsA, + subnetBInfo.BlockchainID, + batchMessengerAddressB, + common.Address{}, + big.NewInt(0), + big.NewInt(int64(300000*numMessages)), + sentMessages.List(), + ) + Expect(err).Should(BeNil()) + + utils.WaitForTransactionSuccess(ctx, subnetAInfo, tx.Hash()) + + // Wait for the message on the destination + maxWait := 30 + currWait := 0 + log.Info("Waiting to receive all messages on destination...") + for { + receivedMessages, err := batchMessengerB.GetCurrentMessages(&bind.CallOpts{}, subnetAInfo.BlockchainID) + Expect(err).Should(BeNil()) + + // Remove the received messages from the set of sent messages + sentMessages.Remove(receivedMessages...) + if sentMessages.Len() == 0 { + break + } + currWait++ + if currWait == maxWait { + Expect(false).Should(BeTrue(), fmt.Sprintf("did not receive all sent messages in time. received %d/%d", numMessages-sentMessages.Len(), numMessages)) + } + time.Sleep(1 * time.Second) + } +} diff --git a/tests/contracts/foundry.toml b/tests/contracts/foundry.toml new file mode 100644 index 00000000..5607c03a --- /dev/null +++ b/tests/contracts/foundry.toml @@ -0,0 +1,26 @@ +[profile.default] +src = 'src' +out = 'out' +libs = ['lib'] +solc_version = '0.8.18' +test = 'tests' + +[fmt] +line_length = 100 +tab_width = 4 +bracket_spacing = false +int_types = 'preserve' +multiline_func_header = 'params_first' +quote_style = 'double' +number_underscores = 'thousands' +override_spacing = true +wrap_comments = false + +# Add the following to your VSCode settings to enable automatic formatting. +# { +# "editor.formatOnSave": true, +# "[solidity]": { +# "editor.defaultFormatter": "JuanBlanco.solidity" +# }, +# "solidity.formatter": "forge", +# } diff --git a/tests/contracts/lib/forge-std b/tests/contracts/lib/forge-std new file mode 160000 index 00000000..d44c4fbb --- /dev/null +++ b/tests/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit d44c4fbbb9ff054fb334babbdd34f9b6e899b3d6 diff --git a/tests/contracts/lib/teleporter b/tests/contracts/lib/teleporter new file mode 160000 index 00000000..8b7d7245 --- /dev/null +++ b/tests/contracts/lib/teleporter @@ -0,0 +1 @@ +Subproject commit 8b7d7245d6998f14a0655b86670c998ba5e5f964 diff --git a/tests/contracts/remappings.txt b/tests/contracts/remappings.txt new file mode 100644 index 00000000..35224d95 --- /dev/null +++ b/tests/contracts/remappings.txt @@ -0,0 +1,3 @@ +@openzeppelin/contracts@4.8.1/=lib/teleporter/contracts/lib/openzeppelin-contracts/contracts/ +@avalabs/subnet-evm-contracts@1.2.0/=lib/teleporter/contracts/lib/subnet-evm/contracts/ +@teleporter/=lib/teleporter/contracts/src/Teleporter/ diff --git a/tests/contracts/src/BatchCrossChainMessenger.sol b/tests/contracts/src/BatchCrossChainMessenger.sol new file mode 100644 index 00000000..33b0f471 --- /dev/null +++ b/tests/contracts/src/BatchCrossChainMessenger.sol @@ -0,0 +1,129 @@ +// (c) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// SPDX-License-Identifier: Ecosystem + +pragma solidity 0.8.18; + +import {TeleporterMessageInput, TeleporterFeeInfo} from "@teleporter/ITeleporterMessenger.sol"; +import {SafeERC20TransferFrom, SafeERC20} from "@teleporter/SafeERC20TransferFrom.sol"; +import {TeleporterOwnerUpgradeable} from "@teleporter/upgrades/TeleporterOwnerUpgradeable.sol"; +import {IERC20} from "@openzeppelin/contracts@4.8.1/token/ERC20/IERC20.sol"; +import {ReentrancyGuard} from "@openzeppelin/contracts@4.8.1/security/ReentrancyGuard.sol"; + +/** + * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. + * DO NOT USE THIS CODE IN PRODUCTION. + */ + +/** + * @dev BatchCrossChainMessenger batches multiple Teleporter messages into a single transaction + */ +contract BatchCrossChainMessenger is ReentrancyGuard, TeleporterOwnerUpgradeable { + using SafeERC20 for IERC20; + + // Messages sent to this contract. + struct Messages { + address sender; + string[] messages; + } + + mapping(bytes32 sourceBlockchainID => string[] messages) private _messages; + + /** + * @dev Emitted when a message is submited to be sent. + */ + event SendMessages( + bytes32 indexed destinationBlockchainID, + address indexed destinationAddress, + address feeTokenAddress, + uint256 feeAmount, + uint256 requiredGasLimit, + string[] messages + ); + + /** + * @dev Emitted when a new message is received from a given chain ID. + */ + event ReceiveMessage( + bytes32 indexed sourceBlockchainID, address indexed originSenderAddress, string message + ); + + constructor( + address teleporterRegistryAddress, + address teleporterManager + ) TeleporterOwnerUpgradeable(teleporterRegistryAddress, teleporterManager) {} + + /** + * @dev Sends a message to another chain. + * @return The message ID of the newly sent message. + */ + function sendMessages( + bytes32 destinationBlockchainID, + address destinationAddress, + address feeTokenAddress, + uint256 feeAmount, + uint256 requiredGasLimit, + string[] memory messages + ) external nonReentrant returns (bytes32[] memory) { + // For non-zero fee amounts, first transfer the fee to this contract. + uint256 adjustedFeeAmount; + if (feeAmount > 0) { + adjustedFeeAmount = + SafeERC20TransferFrom.safeTransferFrom(IERC20(feeTokenAddress), feeAmount); + } + + emit SendMessages({ + destinationBlockchainID: destinationBlockchainID, + destinationAddress: destinationAddress, + feeTokenAddress: feeTokenAddress, + feeAmount: adjustedFeeAmount, + requiredGasLimit: requiredGasLimit, + messages: messages + }); + bytes32[] memory messageIDs = new bytes32[](messages.length); + for (uint256 i = 0; i < messages.length; i++) { + bytes32 messageID = _sendTeleporterMessage( + TeleporterMessageInput({ + destinationBlockchainID: destinationBlockchainID, + destinationAddress: destinationAddress, + feeInfo: TeleporterFeeInfo({feeTokenAddress: feeTokenAddress, amount: adjustedFeeAmount}), + requiredGasLimit: requiredGasLimit, + allowedRelayerAddresses: new address[](0), + message: abi.encode(messages[i]) + }) + ); + messageIDs[i] = messageID; + } + return messageIDs; + } + + /** + * @dev Returns the current message from another chain. + * @return The sender of the message, and the message itself. + */ + function getCurrentMessages(bytes32 sourceBlockchainID) + external + view + returns (string[] memory) + { + string[] memory messages = _messages[sourceBlockchainID]; + return messages; + } + + /** + * @dev See {TeleporterUpgradeable-receiveTeleporterMessage}. + * + * Receives a message from another chain. + */ + function _receiveTeleporterMessage( + bytes32 sourceBlockchainID, + address originSenderAddress, + bytes memory message + ) internal override { + // Store the message. + string memory messageString = abi.decode(message, (string)); + _messages[sourceBlockchainID].push(messageString); + emit ReceiveMessage(sourceBlockchainID, originSenderAddress, messageString); + } +} diff --git a/tests/e2e_test.go b/tests/e2e_test.go index b5c6ffa1..1e1b8df5 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -77,4 +77,7 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { ginkgo.It("Allowed Addresses", func() { AllowedAddresses(localNetworkInstance) }) + ginkgo.It("Batch Message", func() { + BatchRelay(localNetworkInstance) + }) }) diff --git a/tests/utils/utils.go b/tests/utils/utils.go index 07993586..bb11c47b 100644 --- a/tests/utils/utils.go +++ b/tests/utils/utils.go @@ -7,7 +7,6 @@ import ( "bufio" "context" "crypto/ecdsa" - "encoding/hex" "encoding/json" "fmt" "math/big" @@ -21,6 +20,8 @@ import ( warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/awm-relayer/config" offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" + batchcrosschainmessenger "github.com/ava-labs/awm-relayer/tests/abi-bindings/go/BatchCrossChainMessenger" + relayerUtils "github.com/ava-labs/awm-relayer/utils" "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/precompile/contracts/warp" @@ -40,8 +41,10 @@ import ( // Write the test database to /tmp since the data is not needed after the test var StorageLocation = fmt.Sprintf("%s/.awm-relayer-storage", os.TempDir()) -const DefaultRelayerCfgFname = "relayer-config.json" -const DBUpdateSeconds = 1 +const ( + DefaultRelayerCfgFname = "relayer-config.json" + DBUpdateSeconds = 1 +) func BuildAndRunRelayerExecutable(ctx context.Context, relayerConfigPath string) context.CancelFunc { // Build the awm-relayer binary @@ -166,7 +169,7 @@ func CreateDefaultRelayerConfig( RPCEndpoint: config.APIConfig{ BaseURL: fmt.Sprintf("http://%s:%d/ext/bc/%s/rpc", host, port, subnetInfo.BlockchainID.String()), }, - AccountPrivateKey: hex.EncodeToString(relayerKey.D.Bytes()), + AccountPrivateKey: relayerUtils.PrivateKeyToString(relayerKey), } log.Info( @@ -215,6 +218,42 @@ func FundRelayers( } } +func SendBasicTeleporterMessageAsync( + ctx context.Context, + source interfaces.SubnetTestInfo, + destination interfaces.SubnetTestInfo, + fundedKey *ecdsa.PrivateKey, + destinationAddress common.Address, + ids chan<- ids.ID, +) { + input := teleportermessenger.TeleporterMessageInput{ + DestinationBlockchainID: destination.BlockchainID, + DestinationAddress: destinationAddress, + FeeInfo: teleportermessenger.TeleporterFeeInfo{ + FeeTokenAddress: common.Address{}, + Amount: big.NewInt(0), + }, + RequiredGasLimit: big.NewInt(1), + AllowedRelayerAddresses: []common.Address{}, + Message: []byte{1, 2, 3, 4}, + } + + // Send a transaction to the Teleporter contract + log.Info( + "Sending teleporter transaction", + "sourceBlockchainID", source.BlockchainID, + "destinationBlockchainID", destination.BlockchainID, + ) + _, teleporterMessageID := teleporterTestUtils.SendCrossChainMessageAndWaitForAcceptance( + ctx, + source, + destination, + input, + fundedKey, + ) + ids <- teleporterMessageID +} + func SendBasicTeleporterMessage( ctx context.Context, source interfaces.SubnetTestInfo, @@ -415,3 +454,26 @@ func TriggerProcessMissedBlocks( Expect(delivered2).Should(BeFalse()) Expect(delivered3).Should(BeTrue()) } + +func DeployBatchCrossChainMessenger( + ctx context.Context, + senderKey *ecdsa.PrivateKey, + teleporterManager common.Address, + subnet interfaces.SubnetTestInfo, +) (common.Address, *batchcrosschainmessenger.BatchCrossChainMessenger) { + opts, err := bind.NewKeyedTransactorWithChainID( + senderKey, subnet.EVMChainID) + Expect(err).Should(BeNil()) + address, tx, exampleMessenger, err := batchcrosschainmessenger.DeployBatchCrossChainMessenger( + opts, + subnet.RPCClient, + subnet.TeleporterRegistryAddress, + teleporterManager, + ) + Expect(err).Should(BeNil()) + + // Wait for the transaction to be mined + utils.WaitForTransactionSuccess(ctx, subnet, tx.Hash()) + + return address, exampleMessenger +} diff --git a/types/types.go b/types/types.go index f9df9ffd..094a1629 100644 --- a/types/types.go +++ b/types/types.go @@ -8,6 +8,7 @@ import ( "errors" "math/big" + avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" "github.com/ava-labs/subnet-evm/interfaces" @@ -20,19 +21,19 @@ var ErrInvalidLog = errors.New("invalid warp message log") // WarpBlockInfo describes the block height and logs needed to process Warp messages. // WarpBlockInfo instances are populated by the subscriber, and forwared to the -// listener to process +// Listener to process type WarpBlockInfo struct { BlockNumber uint64 - WarpLogs []types.Log + Messages []*WarpMessageInfo } -// WarpLogInfo describes the transaction information for the Warp message -// sent on the source chain, and includes the Warp Message payload bytes -// WarpLogInfo instances are either derived from the logs of a block or -// from the manual Warp message information provided via configuration -type WarpLogInfo struct { - SourceAddress common.Address - UnsignedMsgBytes []byte +// WarpMessageInfo describes the transaction information for the Warp message +// sent on the source chain. +// WarpMessageInfo instances are either derived from the logs of a block or +// from the manual Warp message information provided via configuration. +type WarpMessageInfo struct { + SourceAddress common.Address + UnsignedMessage *avalancheWarp.UnsignedMessage } // Extract Warp logs from the block, if they exist @@ -53,24 +54,50 @@ func NewWarpBlockInfo(header *types.Header, ethClient ethclient.Client) (*WarpBl return nil, err } } + messages := make([]*WarpMessageInfo, len(logs)) + for i, log := range logs { + warpLog, err := NewWarpMessageInfo(log) + if err != nil { + return nil, err + } + messages[i] = warpLog + } + return &WarpBlockInfo{ BlockNumber: header.Number.Uint64(), - WarpLogs: logs, + Messages: messages, }, nil } // Extract the Warp message information from the raw log -func NewWarpLogInfo(log types.Log) (*WarpLogInfo, error) { +func NewWarpMessageInfo(log types.Log) (*WarpMessageInfo, error) { if len(log.Topics) != 3 { return nil, ErrInvalidLog } if log.Topics[0] != WarpPrecompileLogFilter { return nil, ErrInvalidLog } + unsignedMsg, err := UnpackWarpMessage(log.Data) + if err != nil { + return nil, err + } - return &WarpLogInfo{ - // BytesToAddress takes the last 20 bytes of the byte array if it is longer than 20 bytes - SourceAddress: common.BytesToAddress(log.Topics[1][:]), - UnsignedMsgBytes: log.Data, + return &WarpMessageInfo{ + SourceAddress: common.BytesToAddress(log.Topics[1][:]), + UnsignedMessage: unsignedMsg, }, nil } + +func UnpackWarpMessage(unsignedMsgBytes []byte) (*avalancheWarp.UnsignedMessage, error) { + unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(unsignedMsgBytes) + if err != nil { + // If we failed to parse the message as a log, attempt to parse it as a standalone message + var standaloneErr error + unsignedMsg, standaloneErr = avalancheWarp.ParseUnsignedMessage(unsignedMsgBytes) + if standaloneErr != nil { + err = errors.Join(err, standaloneErr) + return nil, err + } + } + return unsignedMsg, nil +} diff --git a/utils/utils.go b/utils/utils.go index 354f5460..17feeb49 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -4,6 +4,8 @@ package utils import ( + "crypto/ecdsa" + "encoding/hex" "errors" "math/big" "strings" @@ -58,6 +60,11 @@ func BigToHashSafe(in *big.Int) (common.Hash, error) { return common.BytesToHash(bytes), nil } +func PrivateKeyToString(key *ecdsa.PrivateKey) string { + // Use FillBytes so leading zeroes are not stripped. + return hex.EncodeToString(key.D.FillBytes(make([]byte, 32))) +} + // SanitizeHexString removes the "0x" prefix from a hex string if it exists. // Otherwise, returns the original string. func SanitizeHexString(hex string) string { diff --git a/vms/evm/destination_client.go b/vms/evm/destination_client.go index 9878f9dc..354ea689 100644 --- a/vms/evm/destination_client.go +++ b/vms/evm/destination_client.go @@ -46,7 +46,10 @@ type destinationClient struct { logger logging.Logger } -func NewDestinationClient(logger logging.Logger, destinationBlockchain *config.DestinationBlockchain) (*destinationClient, error) { +func NewDestinationClient( + logger logging.Logger, + destinationBlockchain *config.DestinationBlockchain, +) (*destinationClient, error) { // Dial the destination RPC endpoint client, err := ethclient.DialWithConfig( context.Background(), @@ -109,15 +112,12 @@ func NewDestinationClient(logger logging.Logger, destinationBlockchain *config.D }, nil } -func (c *destinationClient) SendTx(signedMessage *avalancheWarp.Message, +func (c *destinationClient) SendTx( + signedMessage *avalancheWarp.Message, toAddress string, gasLimit uint64, callData []byte, ) error { - // Synchronize teleporter message requests to the same destination chain so that message ordering is preserved - c.lock.Lock() - defer c.lock.Unlock() - // Get the current base fee estimation, which is based on the previous blocks gas usage. baseFee, err := c.client.EstimateBaseFee(context.Background()) if err != nil { @@ -143,6 +143,12 @@ func (c *destinationClient) SendTx(signedMessage *avalancheWarp.Message, gasFeeCap := baseFee.Mul(baseFee, big.NewInt(BaseFeeFactor)) gasFeeCap.Add(gasFeeCap, big.NewInt(MaxPriorityFeePerGas)) + // Synchronize nonce access so that we send transactions in nonce order. + // Hold the lock until the transaction is sent to minimize the chance of + // an out-of-order transaction being dropped from the mempool. + c.lock.Lock() + defer c.lock.Unlock() + // Construct the actual transaction to broadcast on the destination chain tx := predicateutils.NewPredicateTx( c.evmChainID, @@ -175,9 +181,6 @@ func (c *destinationClient) SendTx(signedMessage *avalancheWarp.Message, ) return err } - - // Increment the nonce to use on the destination chain now that we've sent - // a transaction using the current value. c.currentNonce++ c.logger.Info( "Sent transaction", diff --git a/vms/evm/subscriber.go b/vms/evm/subscriber.go index 5b0a36a2..d5074b78 100644 --- a/vms/evm/subscriber.go +++ b/vms/evm/subscriber.go @@ -57,7 +57,7 @@ func (s *subscriber) ProcessFromHeight(height *big.Int, done chan bool) { zap.String("blockchainID", s.blockchainID.String()), ) if height == nil { - s.logger.Error("cannot process logs from nil height") + s.logger.Error("Cannot process logs from nil height") done <- false return } @@ -89,7 +89,7 @@ func (s *subscriber) ProcessFromHeight(height *big.Int, done chan bool) { err = s.processBlockRange(fromBlock, toBlock) if err != nil { - s.logger.Error("failed to process block range", zap.Error(err)) + s.logger.Error("Failed to process block range", zap.Error(err)) done <- false return }