From 315049b698868b58486a01f330ad3e36af0b109d Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Fri, 7 Jun 2024 10:09:33 -0400 Subject: [PATCH 01/56] Refactor application relayers --- main/main.go | 291 +++++++++++++++-------------- relayer/listener.go | 332 ++++++++------------------------- relayer/message_coordinator.go | 232 +++++++++++++++++++++++ types/types.go | 3 +- vms/destination_client.go | 4 +- 5 files changed, 459 insertions(+), 403 deletions(-) create mode 100644 relayer/message_coordinator.go diff --git a/main/main.go b/main/main.go index 58995590..d478356e 100644 --- a/main/main.go +++ b/main/main.go @@ -11,6 +11,10 @@ import ( "net/http" "os" + "github.com/ava-labs/awm-relayer/messages" + offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" + "github.com/ava-labs/awm-relayer/messages/teleporter" + "github.com/alexliesenfeld/health" "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/ids" @@ -101,18 +105,14 @@ func main() { logger.Info("Initializing destination clients") destinationClients, err := vms.CreateDestinationClients(logger, cfg) if err != nil { - logger.Error( - "Failed to create destination clients", - zap.Error(err), - ) + logger.Fatal("Failed to create destination clients", zap.Error(err)) panic(err) } // Initialize metrics gathered through prometheus gatherer, registerer, err := initializeMetrics() if err != nil { - logger.Fatal("Failed to set up prometheus metrics", - zap.Error(err)) + logger.Fatal("Failed to set up prometheus metrics", zap.Error(err)) panic(err) } @@ -131,10 +131,7 @@ func main() { &cfg, ) if err != nil { - logger.Error( - "Failed to create app request network", - zap.Error(err), - ) + logger.Fatal("Failed to create app request network", zap.Error(err)) panic(err) } @@ -170,32 +167,23 @@ func main() { startMetricsServer(logger, gatherer, cfg.MetricsPort) - metrics, err := relayer.NewApplicationRelayerMetrics(registerer) + relayerMetrics, err := relayer.NewApplicationRelayerMetrics(registerer) if err != nil { - logger.Error( - "Failed to create application relayer metrics", - zap.Error(err), - ) + logger.Fatal("Failed to create application relayer metrics", zap.Error(err)) panic(err) } // Initialize message creator passed down to relayers for creating app requests. messageCreator, err := message.NewCreator(logger, registerer, "message_creator", constants.DefaultNetworkCompressionType, constants.DefaultNetworkMaximumInboundTimeout) if err != nil { - logger.Error( - "Failed to create message creator", - zap.Error(err), - ) + logger.Fatal("Failed to create message creator", zap.Error(err)) panic(err) } // Initialize the database db, err := database.NewDatabase(logger, &cfg) if err != nil { - logger.Error( - "Failed to create database", - zap.Error(err), - ) + logger.Fatal("Failed to create database", zap.Error(err)) panic(err) } @@ -203,13 +191,32 @@ func main() { ticker := utils.NewTicker(cfg.DBWriteIntervalSeconds) go ticker.Run() + messageHandlerFactories, err := createMessageHandlerFactories(logger, &cfg) + if err != nil { + logger.Fatal("Failed to create Message Handler Factories", zap.Error(err)) + panic(err) + } + + applicationRelayers, minHeights, err := createApplicationRelayers( + context.Background(), + logger, + relayerMetrics, + db, + ticker, + network, + messageCreator, + &cfg, + destinationClients, + ) + relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers) + // Gather manual Warp messages specified in the configuration manualWarpMessages := make(map[ids.ID][]*relayerTypes.WarpMessageInfo) for _, msg := range cfg.ManualWarpMessages { sourceBlockchainID := msg.GetSourceBlockchainID() unsignedMsg, err := types.UnpackWarpMessage(msg.GetUnsignedMessageBytes()) if err != nil { - logger.Error( + logger.Fatal( "Failed to unpack manual Warp message", zap.String("warpMessageBytes", hex.EncodeToString(msg.GetUnsignedMessageBytes())), zap.Error(err), @@ -226,136 +233,147 @@ func main() { // 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) - if err != nil { - logger.Error( - "Invalid subnetID in configuration", - zap.Error(err), - ) - panic(err) - } sourceBlockchain := s - health := atomic.NewBool(true) - relayerHealth[blockchainID] = health + isHealthy := atomic.NewBool(true) + relayerHealth[s.GetBlockchainID()] = isHealthy - // errgroup will cancel the context when the first goroutine returns an error errGroup.Go(func() error { - // 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, - *sourceBlockchain, - network, - 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), - ) + return relayer.GetMessageCoordinator().ProcessManualWarpMessages(logger, manualWarpMessages[sourceBlockchain.GetBlockchainID()], *sourceBlockchain) + }) + // errgroup will cancel the context when the first goroutine returns an error + errGroup.Go(func() error { // runListener runs until it errors or the context is cancelled by another goroutine - return runListener( + return relayer.RunListener( ctx, logger, *sourceBlockchain, - health, - manualWarpMessages[blockchainID], - &cfg, - ethClient, - applicationRelayers, - minHeight, + isHealthy, + cfg.ProcessMissedBlocks, + minHeights[sourceBlockchain.GetBlockchainID()], ) }) } err = errGroup.Wait() - logger.Error( - "Relayer exiting.", - zap.Error(err), - ) + logger.Error("Relayer exiting.", zap.Error(err)) } -// 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, +func createMessageHandlerFactories( logger logging.Logger, - sourceBlockchain config.SourceBlockchain, - relayerHealth *atomic.Bool, - manualWarpMessages []*relayerTypes.WarpMessageInfo, globalConfig *config.Config, - ethClient ethclient.Client, - applicationRelayers map[common.Hash]*relayer.ApplicationRelayer, - minHeight uint64, -) error { - // Create the Listener - listener, err := relayer.NewListener( - logger, - sourceBlockchain, - relayerHealth, - globalConfig, - applicationRelayers, - minHeight, - ethClient, - ) - if err != nil { - return fmt.Errorf("failed to create listener instance: %w", err) +) (map[ids.ID]map[common.Address]messages.MessageHandlerFactory, error) { + messageHandlerFactories := make(map[ids.ID]map[common.Address]messages.MessageHandlerFactory) + for _, sourceBlockchain := range globalConfig.SourceBlockchains { + messageHandlerFactoriesForSource := make(map[common.Address]messages.MessageHandlerFactory) + // Create message managers for each supported message protocol + for addressStr, cfg := range sourceBlockchain.MessageContracts { + address := common.HexToAddress(addressStr) + 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", zap.Error(err)) + return nil, err + } + messageHandlerFactoriesForSource[address] = m + } + messageHandlerFactories[sourceBlockchain.GetBlockchainID()] = messageHandlerFactoriesForSource } - logger.Info( - "Created listener", - zap.String("blockchainID", sourceBlockchain.BlockchainID), - ) - 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), + return messageHandlerFactories, nil +} + +func createApplicationRelayers( + ctx context.Context, + logger logging.Logger, + relayerMetrics *relayer.ApplicationRelayerMetrics, + db database.RelayerDatabase, + ticker *utils.Ticker, + network *peers.AppRequestNetwork, + messageCreator message.Creator, + cfg *config.Config, + destinationClients map[ids.ID]vms.DestinationClient, +) (map[common.Hash]*relayer.ApplicationRelayer, map[ids.ID]uint64, error) { + applicationRelayers := make(map[common.Hash]*relayer.ApplicationRelayer) + minHeights := make(map[ids.ID]uint64) + for _, sourceBlockchain := range cfg.SourceBlockchains { + ethClient, err := ethclient.DialWithConfig( + ctx, + 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 nil, nil, err + } - logger.Info( - "Listener initialized. Listening for messages to relay.", - zap.String("originBlockchainID", sourceBlockchain.BlockchainID), - ) + currentHeight, err := ethClient.BlockNumber(ctx) + if err != nil { + logger.Error("Failed to get current block height", zap.Error(err)) + return nil, nil, err + } + ethClient.Close() + + // Create the ApplicationRelayers + applicationRelayersForSource, minHeight, err := createApplicationRelayersForSourceChain( + ctx, + logger, + relayerMetrics, + db, + ticker, + *sourceBlockchain, + network, + messageCreator, + cfg, + currentHeight, + destinationClients, + ) + if err != nil { + logger.Error( + "Failed to create application relayers", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.Error(err), + ) + return nil, nil, err + } - // Wait for logs from the subscribed node - // Will only return on error or context cancellation - return listener.ProcessLogs(ctx) + for relayerID, applicationRelayer := range applicationRelayersForSource { + applicationRelayers[relayerID] = applicationRelayer + } + minHeights[sourceBlockchain.GetBlockchainID()] = minHeight + + logger.Info( + "Created application relayers", + zap.String("blockchainID", sourceBlockchain.BlockchainID), + ) + } + return applicationRelayers, minHeights, nil } // createApplicationRelayers creates Application Relayers for a given source blockchain. -func createApplicationRelayers( +func createApplicationRelayersForSourceChain( ctx context.Context, logger logging.Logger, metrics *relayer.ApplicationRelayerMetrics, @@ -365,7 +383,7 @@ func createApplicationRelayers( network *peers.AppRequestNetwork, messageCreator message.Creator, cfg *config.Config, - srcEthClient ethclient.Client, + currentHeight uint64, destinationClients map[ids.ID]vms.DestinationClient, ) (map[common.Hash]*relayer.ApplicationRelayer, uint64, error) { // Create the ApplicationRelayers @@ -375,17 +393,8 @@ func createApplicationRelayers( ) 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 + // The Listener begins processing messages starting from the minimum height across all the ApplicationRelayers minHeight := uint64(0) for _, relayerID := range database.GetSourceBlockchainRelayerIDs(&sourceBlockchain) { height, err := database.CalculateStartingBlockHeight( diff --git a/relayer/listener.go b/relayer/listener.go index 052d6f06..f9e0177d 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -8,19 +8,13 @@ import ( "fmt" "math/big" "math/rand" - "sync" "github.com/ava-labs/avalanchego/ids" "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/messages" - 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" - vms "github.com/ava-labs/awm-relayer/vms" - "github.com/ethereum/go-ethereum/common" + "github.com/ava-labs/awm-relayer/vms" "go.uber.org/atomic" "go.uber.org/zap" ) @@ -34,28 +28,57 @@ const ( // Listener handles all messages sent from a given source chain type Listener struct { - 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 + Subscriber vms.Subscriber + currentRequestID uint32 + contractMessage vms.ContractMessage + logger logging.Logger + sourceBlockchain config.SourceBlockchain + catchUpResultChan chan bool + healthStatus *atomic.Bool + processMissedBlocks bool + ethClient ethclient.Client } -func NewListener( +// 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, sourceBlockchain config.SourceBlockchain, relayerHealth *atomic.Bool, - globalConfig *config.Config, - applicationRelayers map[common.Hash]*ApplicationRelayer, + processMissedBlocks bool, + minHeight uint64, +) error { + // Create the Listener + listener, err := newListener( + ctx, + logger, + sourceBlockchain, + relayerHealth, + processMissedBlocks, + minHeight, + ) + if err != nil { + return fmt.Errorf("failed to create listener instance: %w", err) + } + + logger.Info( + "Listener initialized. Listening for messages to relay.", + zap.String("originBlockchainID", sourceBlockchain.BlockchainID), + ) + + // Wait for logs from the subscribed node + // Will only return on error or context cancellation + return listener.ProcessLogs(ctx) +} + +func newListener( + ctx context.Context, + logger logging.Logger, + sourceBlockchain config.SourceBlockchain, + relayerHealth *atomic.Bool, + processMissedBlocks bool, startingHeight uint64, - ethClient ethclient.Client, ) (*Listener, error) { blockchainID, err := ids.FromString(sourceBlockchain.BlockchainID) if err != nil { @@ -66,7 +89,7 @@ func NewListener( return nil, err } ethWSClient, err := ethclient.DialWithConfig( - context.Background(), + ctx, sourceBlockchain.WSEndpoint.BaseURL, sourceBlockchain.WSEndpoint.HTTPHeaders, sourceBlockchain.WSEndpoint.QueryParams, @@ -81,40 +104,6 @@ func NewListener( } sub := vms.NewSubscriber(logger, config.ParseVM(sourceBlockchain.VM), blockchainID, ethWSClient) - // Create message managers for each supported message protocol - messageHandlerFactories := make(map[common.Address]messages.MessageHandlerFactory) - for addressStr, cfg := range sourceBlockchain.MessageContracts { - address := common.HexToAddress(addressStr) - 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", - zap.Error(err), - ) - return nil, err - } - messageHandlerFactories[address] = m - } - // Marks when the listener has finished the catch-up process on startup. // Until that time, we do not know the order in which messages are processed, // since the catch-up process occurs concurrently with normal message processing @@ -123,6 +112,22 @@ func NewListener( // scenario. catchUpResultChan := make(chan bool, 1) + // Dial the eth client + ethRPCClient, err := ethclient.DialWithConfig( + ctx, + 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 + } + logger.Info( "Creating relayer", zap.String("subnetID", sourceBlockchain.GetSubnetID().String()), @@ -131,18 +136,15 @@ func NewListener( zap.String("blockchainIDHex", sourceBlockchain.GetBlockchainID().Hex()), ) lstnr := Listener{ - 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, + Subscriber: sub, + currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision + contractMessage: vms.NewContractMessage(logger, sourceBlockchain), + logger: logger, + sourceBlockchain: sourceBlockchain, + catchUpResultChan: catchUpResultChan, + healthStatus: relayerHealth, + processMissedBlocks: processMissedBlocks, + ethClient: ethRPCClient, } // Open the subscription. We must do this before processing any missed messages, otherwise we may miss an incoming message @@ -156,7 +158,7 @@ func NewListener( return nil, err } - if lstnr.globalConfig.ProcessMissedBlocks { + if lstnr.processMissedBlocks { // 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. @@ -228,34 +230,7 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error { zap.Uint64("blockNumber", block.BlockNumber), ) - // 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", - zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - zap.Error(err), - ) - continue - } - if appRelayer == nil { - lstnr.logger.Debug("Application relayer not found. Skipping message relay") - continue - } - messageHandlers[appRelayer.relayerID.ID] = append(messageHandlers[appRelayer.relayerID.ID], handler) - } - // Initiate message relay of all registered messages - for _, appRelayer := range lstnr.applicationRelayers { - // 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) - } + go GetMessageCoordinator().ProcessWarpBlock(block, errChan) case err := <-lstnr.Subscriber.Err(): lstnr.healthStatus.Store(false) lstnr.logger.Error( @@ -297,162 +272,3 @@ func (lstnr *Listener) reconnectToSubscriber() error { lstnr.healthStatus.Store(true) return nil } - -// 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 -// 2. A match on sourceBlockchainID and destinationBlockchainID, with a specific originSenderAddress and any destinationAddress -// 3. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and a specific destinationAddress -// 4. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and any destinationAddress -func (lstnr *Listener) getApplicationRelayer( - sourceBlockchainID ids.ID, - originSenderAddress common.Address, - destinationBlockchainID ids.ID, - destinationAddress common.Address, -) *ApplicationRelayer { - // Check for an exact match - applicationRelayerID := database.CalculateRelayerID( - sourceBlockchainID, - destinationBlockchainID, - originSenderAddress, - destinationAddress, - ) - if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok { - return applicationRelayer - } - - // Check for a match on sourceBlockchainID and destinationBlockchainID, with a specific originSenderAddress and any destinationAddress - applicationRelayerID = database.CalculateRelayerID( - sourceBlockchainID, - destinationBlockchainID, - originSenderAddress, - database.AllAllowedAddress, - ) - if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok { - return applicationRelayer - } - - // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and a specific destinationAddress - applicationRelayerID = database.CalculateRelayerID( - sourceBlockchainID, - destinationBlockchainID, - database.AllAllowedAddress, - destinationAddress, - ) - if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok { - return applicationRelayer - } - - // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and any destinationAddress - applicationRelayerID = database.CalculateRelayerID( - sourceBlockchainID, - destinationBlockchainID, - database.AllAllowedAddress, - database.AllAllowedAddress, - ) - if applicationRelayer, ok := lstnr.applicationRelayers[applicationRelayerID]; ok { - return applicationRelayer - } - lstnr.logger.Debug( - "Application relayer not found. Skipping message relay.", - zap.String("blockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - zap.String("destinationBlockchainID", destinationBlockchainID.String()), - zap.String("originSenderAddress", originSenderAddress.String()), - zap.String("destinationAddress", destinationAddress.String()), - ) - return nil -} - -// 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. - 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", warpMessageInfo.SourceAddress.Hex()), - ) - return nil, nil, nil - } - messageHandler, err := messageHandlerFactory.NewMessageHandler(warpMessageInfo.UnsignedMessage) - if err != nil { - lstnr.logger.Error( - "Failed to create message handler", - zap.Error(err), - ) - return nil, nil, err - } - - // Fetch the message delivery data - sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, err := messageHandler.GetMessageRoutingInfo() - if err != nil { - lstnr.logger.Error( - "Failed to get message routing information", - zap.Error(err), - ) - return nil, nil, err - } - - lstnr.logger.Info( - "Unpacked warp message", - zap.String("sourceBlockchainID", sourceBlockchainID.String()), - zap.String("originSenderAddress", originSenderAddress.String()), - zap.String("destinationBlockchainID", destinationBlockchainID.String()), - zap.String("destinationAddress", destinationAddress.String()), - zap.String("warpMessageID", warpMessageInfo.UnsignedMessage.ID().String()), - ) - - appRelayer := lstnr.getApplicationRelayer( - sourceBlockchainID, - originSenderAddress, - destinationBlockchainID, - destinationAddress, - ) - if appRelayer == nil { - return nil, nil, nil - } - return appRelayer, messageHandler, nil -} - -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 - } - } - return nil -} diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go new file mode 100644 index 00000000..1eb2602f --- /dev/null +++ b/relayer/message_coordinator.go @@ -0,0 +1,232 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package relayer + +import ( + "github.com/ava-labs/avalanchego/ids" + "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/messages" + relayerTypes "github.com/ava-labs/awm-relayer/types" + "github.com/ethereum/go-ethereum/common" + "go.uber.org/zap" +) + +var globalMessageCoordinator *MessageCoordinator + +type MessageCoordinator struct { + logger logging.Logger + // Maps Source chain ID and protocol address to a Message Handler Factory + MessageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory + ApplicationRelayers map[common.Hash]*ApplicationRelayer +} + +func SetMessageCoordinator( + logger logging.Logger, + messageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory, + applicationRelayers map[common.Hash]*ApplicationRelayer, +) { + globalMessageCoordinator = &MessageCoordinator{ + logger: logger, + MessageHandlerFactories: messageHandlerFactories, + ApplicationRelayers: applicationRelayers, + } +} + +func GetMessageCoordinator() *MessageCoordinator { + return globalMessageCoordinator +} + +// GetAppRelayerMessageHandler 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 (mc *MessageCoordinator) GetAppRelayerMessageHandler( + warpMessageInfo *relayerTypes.WarpMessageInfo, +) ( + *ApplicationRelayer, + messages.MessageHandler, + error, +) { + // Check that the warp message is from a supported message protocol contract address. + messageHandlerFactory, supportedMessageProtocol := mc.MessageHandlerFactories[warpMessageInfo.UnsignedMessage.SourceChainID][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. + mc.logger.Debug( + "Warp message from unsupported message protocol address. Not relaying.", + zap.String("protocolAddress", warpMessageInfo.SourceAddress.Hex()), + ) + return nil, nil, nil + } + messageHandler, err := messageHandlerFactory.NewMessageHandler(warpMessageInfo.UnsignedMessage) + if err != nil { + mc.logger.Error( + "Failed to create message handler", + zap.Error(err), + ) + return nil, nil, err + } + + // Fetch the message delivery data + sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, err := messageHandler.GetMessageRoutingInfo() + if err != nil { + mc.logger.Error( + "Failed to get message routing information", + zap.Error(err), + ) + return nil, nil, err + } + + mc.logger.Info( + "Unpacked warp message", + zap.String("sourceBlockchainID", sourceBlockchainID.String()), + zap.String("originSenderAddress", originSenderAddress.String()), + zap.String("destinationBlockchainID", destinationBlockchainID.String()), + zap.String("destinationAddress", destinationAddress.String()), + zap.String("warpMessageID", warpMessageInfo.UnsignedMessage.ID().String()), + ) + + appRelayer := mc.getApplicationRelayer( + sourceBlockchainID, + originSenderAddress, + destinationBlockchainID, + destinationAddress, + ) + if appRelayer == nil { + return nil, nil, nil + } + return appRelayer, messageHandler, nil +} + +// 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 +// 2. A match on sourceBlockchainID and destinationBlockchainID, with a specific originSenderAddress and any destinationAddress +// 3. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and a specific destinationAddress +// 4. A match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and any destinationAddress +func (mc *MessageCoordinator) getApplicationRelayer( + sourceBlockchainID ids.ID, + originSenderAddress common.Address, + destinationBlockchainID ids.ID, + destinationAddress common.Address, +) *ApplicationRelayer { + // Check for an exact match + applicationRelayerID := database.CalculateRelayerID( + sourceBlockchainID, + destinationBlockchainID, + originSenderAddress, + destinationAddress, + ) + if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + return applicationRelayer + } + + // Check for a match on sourceBlockchainID and destinationBlockchainID, with a specific originSenderAddress and any destinationAddress + applicationRelayerID = database.CalculateRelayerID( + sourceBlockchainID, + destinationBlockchainID, + originSenderAddress, + database.AllAllowedAddress, + ) + if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + return applicationRelayer + } + + // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and a specific destinationAddress + applicationRelayerID = database.CalculateRelayerID( + sourceBlockchainID, + destinationBlockchainID, + database.AllAllowedAddress, + destinationAddress, + ) + if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + return applicationRelayer + } + + // Check for a match on sourceBlockchainID and destinationBlockchainID, with any originSenderAddress and any destinationAddress + applicationRelayerID = database.CalculateRelayerID( + sourceBlockchainID, + destinationBlockchainID, + database.AllAllowedAddress, + database.AllAllowedAddress, + ) + if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + return applicationRelayer + } + mc.logger.Debug( + "Application relayer not found. Skipping message relay.", + zap.String("blockchainID", sourceBlockchainID.String()), + zap.String("destinationBlockchainID", destinationBlockchainID.String()), + zap.String("originSenderAddress", originSenderAddress.String()), + zap.String("destinationAddress", destinationAddress.String()), + ) + return nil +} + +func (mc *MessageCoordinator) 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 := mc.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 + } + } + return nil +} + +func (mc *MessageCoordinator) ProcessWarpBlock(block *relayerTypes.WarpBlockInfo, errChan chan error) { + // 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 := mc.GetAppRelayerMessageHandler(warpLogInfo) + if err != nil { + mc.logger.Error( + "Failed to parse message", + zap.String("blockchainID", warpLogInfo.UnsignedMessage.SourceChainID.String()), + zap.Error(err), + ) + continue + } + if appRelayer == nil { + mc.logger.Debug("Application relayer not found. Skipping message relay") + continue + } + messageHandlers[appRelayer.relayerID.ID] = append(messageHandlers[appRelayer.relayerID.ID], handler) + } + // Initiate message relay of all registered messages + for _, appRelayer := range mc.ApplicationRelayers { + // 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) + } +} diff --git a/types/types.go b/types/types.go index 094a1629..210f0e32 100644 --- a/types/types.go +++ b/types/types.go @@ -20,8 +20,7 @@ var WarpPrecompileLogFilter = warp.WarpABI.Events["SendWarpMessage"].ID 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 +// WarpBlockInfo instances are populated by the subscriber, and forwarded to the Listener to process. type WarpBlockInfo struct { BlockNumber uint64 Messages []*WarpMessageInfo diff --git a/vms/destination_client.go b/vms/destination_client.go index 8060cbfa..12341e23 100644 --- a/vms/destination_client.go +++ b/vms/destination_client.go @@ -20,7 +20,7 @@ import ( // DestinationClient is the interface for the destination chain client. Methods that interact with the destination chain // should generally be implemented in a thread safe way, as they will be called concurrently by the application relayers. type DestinationClient interface { - // SendTx contructs the transaction from warp primitives, and send to the configured destination chain endpoint + // SendTx constructs the transaction from warp primitives, and send to the configured destination chain endpoint // TODO: Make generic for any VM. SendTx(signedMessage *warp.Message, toAddress string, gasLimit uint64, callData []byte) error @@ -51,7 +51,7 @@ func CreateDestinationClients(logger logging.Logger, relayerConfig config.Config if err != nil { logger.Error( "Failed to decode base-58 encoded source chain ID", - zap.String("blockchainID", blockchainID.String()), + zap.String("blockchainID", subnetInfo.BlockchainID), zap.Error(err), ) return nil, err From 6254a6c46e0ca974e2d2872f2f592489b7a59f7f Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Fri, 7 Jun 2024 12:51:12 -0400 Subject: [PATCH 02/56] wip --- main/main.go | 2 +- relayer/listener.go | 6 +++--- relayer/message_coordinator.go | 37 +++++++++++++++++++++------------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/main/main.go b/main/main.go index d478356e..31bbc982 100644 --- a/main/main.go +++ b/main/main.go @@ -239,7 +239,7 @@ func main() { relayerHealth[s.GetBlockchainID()] = isHealthy errGroup.Go(func() error { - return relayer.GetMessageCoordinator().ProcessManualWarpMessages(logger, manualWarpMessages[sourceBlockchain.GetBlockchainID()], *sourceBlockchain) + return relayer.ProcessManualWarpMessages(manualWarpMessages[sourceBlockchain.GetBlockchainID()]) }) // errgroup will cancel the context when the first goroutine returns an error diff --git a/relayer/listener.go b/relayer/listener.go index f9e0177d..a083ac77 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -69,7 +69,7 @@ func RunListener( // Wait for logs from the subscribed node // Will only return on error or context cancellation - return listener.ProcessLogs(ctx) + return listener.processLogs(ctx) } func newListener( @@ -177,7 +177,7 @@ func newListener( // Listens to the Subscriber logs channel to process them. // 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 { +func (lstnr *Listener) processLogs(ctx context.Context) error { // Error channel for application relayer errors errChan := make(chan error) for { @@ -230,7 +230,7 @@ func (lstnr *Listener) ProcessLogs(ctx context.Context) error { zap.Uint64("blockNumber", block.BlockNumber), ) - go GetMessageCoordinator().ProcessWarpBlock(block, errChan) + go ProcessWarpBlock(block, errChan) case err := <-lstnr.Subscriber.Err(): lstnr.healthStatus.Store(false) lstnr.logger.Error( diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 1eb2602f..6f8fd251 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -4,9 +4,10 @@ package relayer import ( + "fmt" + "github.com/ava-labs/avalanchego/ids" "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/messages" relayerTypes "github.com/ava-labs/awm-relayer/types" @@ -35,10 +36,6 @@ func SetMessageCoordinator( } } -func GetMessageCoordinator() *MessageCoordinator { - return globalMessageCoordinator -} - // GetAppRelayerMessageHandler 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 @@ -166,21 +163,26 @@ func (mc *MessageCoordinator) getApplicationRelayer( return nil } -func (mc *MessageCoordinator) ProcessManualWarpMessages( - logger logging.Logger, +func ProcessManualWarpMessages(manualWarpMessages []*relayerTypes.WarpMessageInfo) error { + if globalMessageCoordinator == nil { + return fmt.Errorf("global message coordinator not set") + } + return globalMessageCoordinator.processManualWarpMessages(manualWarpMessages) +} + +func (mc *MessageCoordinator) processManualWarpMessages( manualWarpMessages []*relayerTypes.WarpMessageInfo, - sourceBlockchain config.SourceBlockchain, ) error { // Send any messages that were specified in the configuration for _, warpMessage := range manualWarpMessages { - logger.Info( + mc.logger.Info( "Relaying manual Warp message", - zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), ) appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) if err != nil { - logger.Error( + mc.logger.Error( "Failed to parse manual Warp message.", zap.Error(err), zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), @@ -189,9 +191,9 @@ func (mc *MessageCoordinator) ProcessManualWarpMessages( } err = appRelayer.ProcessMessage(handler) if err != nil { - logger.Error( + mc.logger.Error( "Failed to process manual Warp message", - zap.String("blockchainID", sourceBlockchain.BlockchainID), + zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), ) return err @@ -200,7 +202,14 @@ func (mc *MessageCoordinator) ProcessManualWarpMessages( return nil } -func (mc *MessageCoordinator) ProcessWarpBlock(block *relayerTypes.WarpBlockInfo, errChan chan error) { +func ProcessWarpBlock(block *relayerTypes.WarpBlockInfo, errChan chan error) { + if globalMessageCoordinator == nil { + panic("global message coordinator not set") + } + globalMessageCoordinator.processWarpBlock(block, errChan) +} + +func (mc *MessageCoordinator) processWarpBlock(block *relayerTypes.WarpBlockInfo, errChan chan error) { // Register each message in the block with the appropriate application relayer messageHandlers := make(map[common.Hash][]messages.MessageHandler) for _, warpLogInfo := range block.Messages { From ccd4ef55f1a0dbe21054aaeaf893b8efe80322a0 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Mon, 10 Jun 2024 11:57:39 -0400 Subject: [PATCH 03/56] Functions for relaying message from blockchain ID, warp message ID, and block number --- main/main.go | 51 +++++++++++++++++++-------- relayer/listener.go | 40 +++------------------ relayer/message_coordinator.go | 64 ++++++++++++++++++++++++++-------- types/types.go | 30 ++++++++++++---- 4 files changed, 115 insertions(+), 70 deletions(-) diff --git a/main/main.go b/main/main.go index 31bbc982..ac470333 100644 --- a/main/main.go +++ b/main/main.go @@ -109,6 +109,14 @@ func main() { panic(err) } + // Initialize all source clients + logger.Info("Initializing destination clients") + sourceClients, err := createSourceClients(context.Background(), logger, &cfg) + if err != nil { + logger.Fatal("Failed to create source clients", zap.Error(err)) + panic(err) + } + // Initialize metrics gathered through prometheus gatherer, registerer, err := initializeMetrics() if err != nil { @@ -206,6 +214,7 @@ func main() { network, messageCreator, &cfg, + sourceClients, destinationClients, ) relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers) @@ -249,6 +258,7 @@ func main() { ctx, logger, *sourceBlockchain, + sourceClients[sourceBlockchain.GetBlockchainID()], isHealthy, cfg.ProcessMissedBlocks, minHeights[sourceBlockchain.GetBlockchainID()], @@ -300,21 +310,16 @@ func createMessageHandlerFactories( return messageHandlerFactories, nil } -func createApplicationRelayers( +func createSourceClients( ctx context.Context, logger logging.Logger, - relayerMetrics *relayer.ApplicationRelayerMetrics, - db database.RelayerDatabase, - ticker *utils.Ticker, - network *peers.AppRequestNetwork, - messageCreator message.Creator, cfg *config.Config, - destinationClients map[ids.ID]vms.DestinationClient, -) (map[common.Hash]*relayer.ApplicationRelayer, map[ids.ID]uint64, error) { - applicationRelayers := make(map[common.Hash]*relayer.ApplicationRelayer) - minHeights := make(map[ids.ID]uint64) +) (map[ids.ID]ethclient.Client, error) { + var err error + clients := make(map[ids.ID]ethclient.Client) + for _, sourceBlockchain := range cfg.SourceBlockchains { - ethClient, err := ethclient.DialWithConfig( + clients[sourceBlockchain.GetBlockchainID()], err = ethclient.DialWithConfig( ctx, sourceBlockchain.RPCEndpoint.BaseURL, sourceBlockchain.RPCEndpoint.HTTPHeaders, @@ -326,15 +331,33 @@ func createApplicationRelayers( zap.String("blockchainID", sourceBlockchain.BlockchainID), zap.Error(err), ) - return nil, nil, err + return nil, err } + } + return clients, nil +} - currentHeight, err := ethClient.BlockNumber(ctx) +// Returns a map of application relayers, as well as a map of source blockchain IDs to starting heights. +func createApplicationRelayers( + ctx context.Context, + logger logging.Logger, + relayerMetrics *relayer.ApplicationRelayerMetrics, + db database.RelayerDatabase, + ticker *utils.Ticker, + network *peers.AppRequestNetwork, + messageCreator message.Creator, + cfg *config.Config, + sourceClients map[ids.ID]ethclient.Client, + destinationClients map[ids.ID]vms.DestinationClient, +) (map[common.Hash]*relayer.ApplicationRelayer, map[ids.ID]uint64, error) { + applicationRelayers := make(map[common.Hash]*relayer.ApplicationRelayer) + minHeights := make(map[ids.ID]uint64) + for _, sourceBlockchain := range cfg.SourceBlockchains { + currentHeight, err := sourceClients[sourceBlockchain.GetBlockchainID()].BlockNumber(ctx) if err != nil { logger.Error("Failed to get current block height", zap.Error(err)) return nil, nil, err } - ethClient.Close() // Create the ApplicationRelayers applicationRelayersForSource, minHeight, err := createApplicationRelayersForSourceChain( diff --git a/relayer/listener.go b/relayer/listener.go index a083ac77..07b28d0b 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -13,7 +13,6 @@ import ( "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/ethclient" - relayerTypes "github.com/ava-labs/awm-relayer/types" "github.com/ava-labs/awm-relayer/vms" "go.uber.org/atomic" "go.uber.org/zap" @@ -45,6 +44,7 @@ func RunListener( ctx context.Context, logger logging.Logger, sourceBlockchain config.SourceBlockchain, + ethRPCClient ethclient.Client, relayerHealth *atomic.Bool, processMissedBlocks bool, minHeight uint64, @@ -54,6 +54,7 @@ func RunListener( ctx, logger, sourceBlockchain, + ethRPCClient, relayerHealth, processMissedBlocks, minHeight, @@ -76,6 +77,7 @@ func newListener( ctx context.Context, logger logging.Logger, sourceBlockchain config.SourceBlockchain, + ethRPCClient ethclient.Client, relayerHealth *atomic.Bool, processMissedBlocks bool, startingHeight uint64, @@ -112,22 +114,6 @@ func newListener( // scenario. catchUpResultChan := make(chan bool, 1) - // Dial the eth client - ethRPCClient, err := ethclient.DialWithConfig( - ctx, - 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 - } - logger.Info( "Creating relayer", zap.String("subnetID", sourceBlockchain.GetSubnetID().String()), @@ -212,25 +198,7 @@ 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( - "Failed to create Warp block info", - zap.Error(err), - ) - continue - } - - // Relay the messages in the block to the destination chains. Continue on failure. - lstnr.logger.Debug( - "Processing block", - zap.String("sourceBlockchainID", lstnr.sourceBlockchain.GetBlockchainID().String()), - zap.Uint64("blockNumber", block.BlockNumber), - ) - - go ProcessWarpBlock(block, errChan) + go ProcessBlock(blockHeader, lstnr.ethClient, errChan) case err := <-lstnr.Subscriber.Err(): lstnr.healthStatus.Store(false) lstnr.logger.Error( diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 6f8fd251..03c9e503 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -4,13 +4,17 @@ package relayer import ( + "errors" "fmt" + "math/big" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/awm-relayer/database" + "github.com/ava-labs/awm-relayer/ethclient" "github.com/ava-labs/awm-relayer/messages" relayerTypes "github.com/ava-labs/awm-relayer/types" + "github.com/ava-labs/subnet-evm/core/types" "github.com/ethereum/go-ethereum/common" "go.uber.org/zap" ) @@ -19,9 +23,10 @@ var globalMessageCoordinator *MessageCoordinator type MessageCoordinator struct { logger logging.Logger - // Maps Source chain ID and protocol address to a Message Handler Factory + // Maps Source blockchain ID and protocol address to a Message Handler Factory MessageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory ApplicationRelayers map[common.Hash]*ApplicationRelayer + SourceClients map[ids.ID]ethclient.Client } func SetMessageCoordinator( @@ -60,20 +65,14 @@ func (mc *MessageCoordinator) GetAppRelayerMessageHandler( } messageHandler, err := messageHandlerFactory.NewMessageHandler(warpMessageInfo.UnsignedMessage) if err != nil { - mc.logger.Error( - "Failed to create message handler", - zap.Error(err), - ) + mc.logger.Error("Failed to create message handler", zap.Error(err)) return nil, nil, err } // Fetch the message delivery data sourceBlockchainID, originSenderAddress, destinationBlockchainID, destinationAddress, err := messageHandler.GetMessageRoutingInfo() if err != nil { - mc.logger.Error( - "Failed to get message routing information", - zap.Error(err), - ) + mc.logger.Error("Failed to get message routing information", zap.Error(err)) return nil, nil, err } @@ -202,11 +201,50 @@ func (mc *MessageCoordinator) processManualWarpMessages( return nil } -func ProcessWarpBlock(block *relayerTypes.WarpBlockInfo, errChan chan error) { +func ProcessBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { if globalMessageCoordinator == nil { panic("global message coordinator not set") } - globalMessageCoordinator.processWarpBlock(block, errChan) + globalMessageCoordinator.processBlock(blockHeader, ethClient, errChan) +} + +func (mc *MessageCoordinator) ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { + ethClient, ok := mc.SourceClients[blockchainID] + if !ok { + return fmt.Errorf("source client not set for blockchain ID: %s", blockchainID.String()) + } + + warpMessage, err := relayerTypes.FetchWarpMessageFromID(ethClient, messageID, blockNum) + if err != nil { + return err + } + + appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) + if err != nil { + mc.logger.Error( + "Failed to parse message", + zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), + zap.Error(err), + ) + return err + } + if appRelayer == nil { + mc.logger.Error("Application relayer not found") + return errors.New("application relayer not found") + } + + return appRelayer.ProcessMessage(handler) +} + +func (mc *MessageCoordinator) processBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { + // Parse the logs in the block, and group by application relayer + block, err := relayerTypes.NewWarpBlockInfo(blockHeader, ethClient) + if err != nil { + mc.logger.Error("Failed to create Warp block info", zap.Error(err)) + return + } + + mc.processWarpBlock(block, errChan) } func (mc *MessageCoordinator) processWarpBlock(block *relayerTypes.WarpBlockInfo, errChan chan error) { @@ -234,8 +272,6 @@ func (mc *MessageCoordinator) processWarpBlock(block *relayerTypes.WarpBlockInfo // 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) + appRelayer.ProcessHeight(block.BlockNumber, handlers, errChan) } } diff --git a/types/types.go b/types/types.go index 363586c2..03f003fc 100644 --- a/types/types.go +++ b/types/types.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "math/big" "time" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -49,7 +50,7 @@ func NewWarpBlockInfo(header *types.Header, ethClient ethclient.Client) (*WarpBl ) // Check if the block contains warp logs, and fetch them from the client if it does if header.Bloom.Test(WarpPrecompileLogFilter[:]) { - logs, err = fetchWarpLogsWithRetries(ethClient, header, filterLogsRetries, retryInterval) + logs, err = fetchWarpLogsWithRetries(ethClient, header.Number, filterLogsRetries, retryInterval) if err != nil { return nil, err } @@ -102,9 +103,26 @@ func UnpackWarpMessage(unsignedMsgBytes []byte) (*avalancheWarp.UnsignedMessage, return unsignedMsg, nil } +func FetchWarpMessageFromID(ethClient ethclient.Client, warpMessageID common.Hash, blockNum *big.Int) (*WarpMessageInfo, error) { + logs, err := ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{ + Topics: [][]common.Hash{{WarpPrecompileLogFilter}, nil, {warpMessageID}}, + Addresses: []common.Address{warp.ContractAddress}, + FromBlock: blockNum, + ToBlock: blockNum, + }) + if err != nil { + return nil, err + } + if len(logs) != 1 { + return nil, ErrInvalidLog + } + + return NewWarpMessageInfo(logs[0]) +} + // The node serving the filter logs request may be behind the node serving the block header request, // so we retry a few times to ensure we get the logs -func fetchWarpLogsWithRetries(ethClient ethclient.Client, header *types.Header, numRetries int, retryInterval time.Duration) ([]types.Log, error) { +func fetchWarpLogsWithRetries(ethClient ethclient.Client, blockNum *big.Int, numRetries int, retryInterval time.Duration) ([]types.Log, error) { var ( logs []types.Log err error @@ -112,10 +130,10 @@ func fetchWarpLogsWithRetries(ethClient ethclient.Client, header *types.Header, for i := 0; i < numRetries; i++ { logs, err = ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{ - Topics: [][]common.Hash{{WarpPrecompileLogFilter}}, + Topics: [][]common.Hash{{WarpPrecompileLogFilter}, nil}, Addresses: []common.Address{warp.ContractAddress}, - FromBlock: header.Number, - ToBlock: header.Number, + FromBlock: blockNum, + ToBlock: blockNum, }) if err == nil { return logs, nil @@ -124,5 +142,5 @@ func fetchWarpLogsWithRetries(ethClient ethclient.Client, header *types.Header, time.Sleep(retryInterval) } } - return nil, fmt.Errorf("failed to fetch warp logs for block %d after %d retries: %w", header.Number.Uint64(), filterLogsRetries, err) + return nil, fmt.Errorf("failed to fetch warp logs for block %d after %d retries: %w", blockNum.Uint64(), filterLogsRetries, err) } From a6fb5cfed6c2a8ca099f1d172522dc69e6d8bf36 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Mon, 10 Jun 2024 12:04:23 -0400 Subject: [PATCH 04/56] lint --- main/main.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/main/main.go b/main/main.go index ac470333..e943489e 100644 --- a/main/main.go +++ b/main/main.go @@ -217,6 +217,10 @@ func main() { sourceClients, destinationClients, ) + if err != nil { + logger.Fatal("Failed to create Application Relayers", zap.Error(err)) + panic(err) + } relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers) // Gather manual Warp messages specified in the configuration From d67e619115dd3b7239dd2ae55f4fee718d9f0efa Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Mon, 10 Jun 2024 12:28:27 -0400 Subject: [PATCH 05/56] Fixes --- relayer/message_coordinator.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 03c9e503..c7cee4f1 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -201,17 +201,10 @@ func (mc *MessageCoordinator) processManualWarpMessages( return nil } -func ProcessBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { - if globalMessageCoordinator == nil { - panic("global message coordinator not set") - } - globalMessageCoordinator.processBlock(blockHeader, ethClient, errChan) -} - func (mc *MessageCoordinator) ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { ethClient, ok := mc.SourceClients[blockchainID] if !ok { - return fmt.Errorf("source client not set for blockchain ID: %s", blockchainID.String()) + return fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) } warpMessage, err := relayerTypes.FetchWarpMessageFromID(ethClient, messageID, blockNum) @@ -236,18 +229,23 @@ func (mc *MessageCoordinator) ProcessMessage(blockchainID ids.ID, messageID comm return appRelayer.ProcessMessage(handler) } +func ProcessBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { + if globalMessageCoordinator == nil { + panic("global message coordinator not set") + } + globalMessageCoordinator.processBlock(blockHeader, ethClient, errChan) +} + +// Meant to be ran asynchronously. Errors should be sent to errChan. func (mc *MessageCoordinator) processBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { // Parse the logs in the block, and group by application relayer block, err := relayerTypes.NewWarpBlockInfo(blockHeader, ethClient) if err != nil { mc.logger.Error("Failed to create Warp block info", zap.Error(err)) + errChan <- err return } - mc.processWarpBlock(block, errChan) -} - -func (mc *MessageCoordinator) processWarpBlock(block *relayerTypes.WarpBlockInfo, errChan chan error) { // Register each message in the block with the appropriate application relayer messageHandlers := make(map[common.Hash][]messages.MessageHandler) for _, warpLogInfo := range block.Messages { @@ -256,6 +254,7 @@ func (mc *MessageCoordinator) processWarpBlock(block *relayerTypes.WarpBlockInfo mc.logger.Error( "Failed to parse message", zap.String("blockchainID", warpLogInfo.UnsignedMessage.SourceChainID.String()), + zap.String("protocolAddress", warpLogInfo.SourceAddress.String()), zap.Error(err), ) continue @@ -272,6 +271,6 @@ func (mc *MessageCoordinator) processWarpBlock(block *relayerTypes.WarpBlockInfo // An empty slice is still a valid argument to ProcessHeight; in this case the height is immediately committed. handlers := messageHandlers[appRelayer.relayerID.ID] - appRelayer.ProcessHeight(block.BlockNumber, handlers, errChan) + go appRelayer.ProcessHeight(block.BlockNumber, handlers, errChan) } } From 96ecc68ac5da45ab398702b8c9a6af724f974ab2 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 11 Jun 2024 12:14:14 -0400 Subject: [PATCH 06/56] WIP --- main/main.go | 16 ++--- relayer/message_coordinator.go | 9 ++- relayer/relay_message_api_handler.go | 48 +++++++++++++++ tests/basic_relay.go | 6 +- tests/e2e_test.go | 3 + tests/relay_message_api.go | 90 ++++++++++++++++++++++++++++ 6 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 relayer/relay_message_api_handler.go create mode 100644 tests/relay_message_api.go diff --git a/main/main.go b/main/main.go index e943489e..805ce920 100644 --- a/main/main.go +++ b/main/main.go @@ -166,13 +166,6 @@ func main() { }), ) - http.Handle("/health", health.NewHandler(checker)) - - // start the health check server - go func() { - log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", cfg.APIPort), nil)) - }() - startMetricsServer(logger, gatherer, cfg.MetricsPort) relayerMetrics, err := relayer.NewApplicationRelayerMetrics(registerer) @@ -223,6 +216,15 @@ func main() { } relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers) + // Initialize the API after the message coordinator is set + http.Handle("/health", health.NewHandler(checker)) + http.Handle("/relay-message", relayer.RelayMessageAPIHandler()) + + // start the health check server + go func() { + log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", cfg.APIPort), nil)) + }() + // Gather manual Warp messages specified in the configuration manualWarpMessages := make(map[ids.ID][]*relayerTypes.WarpMessageInfo) for _, msg := range cfg.ManualWarpMessages { diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index c7cee4f1..e27a42bd 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -201,7 +201,14 @@ func (mc *MessageCoordinator) processManualWarpMessages( return nil } -func (mc *MessageCoordinator) ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { +func ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { + if globalMessageCoordinator == nil { + panic("global message coordinator not set") + } + return globalMessageCoordinator.processMessage(blockchainID, messageID, blockNum) +} + +func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { ethClient, ok := mc.SourceClients[blockchainID] if !ok { return fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go new file mode 100644 index 00000000..71cb9f2b --- /dev/null +++ b/relayer/relay_message_api_handler.go @@ -0,0 +1,48 @@ +package relayer + +import ( + "encoding/json" + "math/big" + "net/http" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ethereum/go-ethereum/common" +) + +type RelayMessageRequest struct { + BlockchainID string `json:"blockchain-id"` + MessageID string `json:"message-id"` + BlockNum string `json:"block-num"` +} + +func RelayMessageAPIHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req RelayMessageRequest + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + blockchainID, err := ids.FromString(req.BlockchainID) + if err != nil { + http.Error(w, "invalid blockchainID"+err.Error(), http.StatusBadRequest) + return + } + messageID := common.HexToHash(req.MessageID) + blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) + if !ok { + http.Error(w, "invalid blockNum", http.StatusBadRequest) + return + } + + err = ProcessMessage(blockchainID, messageID, blockNum) + if err != nil { + http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte("Message processed successfully")) + } +} diff --git a/tests/basic_relay.go b/tests/basic_relay.go index 1c2a52df..0021a8ba 100644 --- a/tests/basic_relay.go +++ b/tests/basic_relay.go @@ -114,8 +114,10 @@ func BasicRelay(network interfaces.LocalNetwork) { relayerIDA := database.CalculateRelayerID(subnetAInfo.BlockchainID, subnetBInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress) relayerIDB := database.CalculateRelayerID(subnetBInfo.BlockchainID, subnetAInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress) // Modify the JSON database to force the relayer to re-process old blocks - jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0")) - jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0")) + err = jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0")) + Expect(err).Should(BeNil()) + err = jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0")) + Expect(err).Should(BeNil()) // Subscribe to the destination chain newHeadsB := make(chan *types.Header, 10) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 1e1b8df5..d6c445e1 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -80,4 +80,7 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { ginkgo.It("Batch Message", func() { BatchRelay(localNetworkInstance) }) + ginkgo.FIt("Relay Message API", func() { + RelayMessageAPI(localNetworkInstance) + }) }) diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go new file mode 100644 index 00000000..bb56b5b2 --- /dev/null +++ b/tests/relay_message_api.go @@ -0,0 +1,90 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package tests + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/ava-labs/awm-relayer/relayer" + + testUtils "github.com/ava-labs/awm-relayer/tests/utils" + "github.com/ava-labs/teleporter/tests/interfaces" + "github.com/ava-labs/teleporter/tests/utils" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + . "github.com/onsi/gomega" +) + +func RelayMessageAPI(network interfaces.LocalNetwork) { + ctx := context.Background() + subnetAInfo := network.GetPrimaryNetworkInfo() + subnetBInfo, _ := utils.GetTwoSubnets(network) + fundedAddress, fundedKey := network.GetFundedAccountInfo() + teleporterContractAddress := network.GetTeleporterContractAddress() + err := testUtils.ClearRelayerStorage() + Expect(err).Should(BeNil()) + + 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) + + log.Info("Sending teleporter messages") + receipt1, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) + warpMessage1 := getWarpMessageFromLog(ctx, receipt1, subnetAInfo) + receipt2, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) + warpMessage2 := getWarpMessageFromLog(ctx, receipt2, subnetAInfo) + + // Set up relayer config + relayerConfig := testUtils.CreateDefaultRelayerConfig( + []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, + []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, + teleporterContractAddress, + fundedAddress, + relayerKey, + ) + // Don't process missed blocks, so we can manually relay + relayerConfig.ProcessMissedBlocks = false + + 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) + + reqBody := relayer.RelayMessageRequest{ + BlockchainID: subnetAInfo.BlockchainID.String(), + MessageID: warpMessage1.ID().String(), + BlockNum: receipt1.BlockNumber.String(), + } + + b, err := json.Marshal(reqBody) + Expect(err).Should(BeNil()) + bodyReader := bytes.NewReader(b) + + requestURL := fmt.Sprintf("http://localhost:%d", relayerConfig.APIPort) + req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) + Expect(err).Should(BeNil()) + + client := http.Client{ + Timeout: 30 * time.Second, + } + + res, err := client.Do(req) + Expect(err).Should(BeNil()) + + res. + + // Cancel the command and stop the relayer + relayerCleanup() +} From 770383126a5ca557328696264690f83ef135283a Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 11 Jun 2024 12:35:31 -0400 Subject: [PATCH 07/56] Add test for API --- relayer/message_coordinator.go | 2 +- tests/e2e_test.go | 2 +- tests/relay_message_api.go | 8 ++++---- types/types.go | 27 --------------------------- 4 files changed, 6 insertions(+), 33 deletions(-) diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index e27a42bd..c14127af 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -11,10 +11,10 @@ import ( "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/awm-relayer/database" - "github.com/ava-labs/awm-relayer/ethclient" "github.com/ava-labs/awm-relayer/messages" relayerTypes "github.com/ava-labs/awm-relayer/types" "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/ethclient" "github.com/ethereum/go-ethereum/common" "go.uber.org/zap" ) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index d6c445e1..7973893f 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -80,7 +80,7 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { ginkgo.It("Batch Message", func() { BatchRelay(localNetworkInstance) }) - ginkgo.FIt("Relay Message API", func() { + ginkgo.It("Relay Message API", func() { RelayMessageAPI(localNetworkInstance) }) }) diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index bb56b5b2..ae52b7db 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -40,6 +40,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { warpMessage1 := getWarpMessageFromLog(ctx, receipt1, subnetAInfo) receipt2, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) warpMessage2 := getWarpMessageFromLog(ctx, receipt2, subnetAInfo) + warpMessage2.ID() // Set up relayer config relayerConfig := testUtils.CreateDefaultRelayerConfig( @@ -82,9 +83,8 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { res, err := client.Do(req) Expect(err).Should(BeNil()) + Expect(res.Status).Should(Equal(http.StatusOK)) - res. - - // Cancel the command and stop the relayer - relayerCleanup() + // Cancel the command and stop the relayer + relayerCleanup() } diff --git a/types/types.go b/types/types.go index f5e8b94b..8029b038 100644 --- a/types/types.go +++ b/types/types.go @@ -6,9 +6,7 @@ package types import ( "context" "errors" - "fmt" "math/big" - "time" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/awm-relayer/utils" @@ -126,28 +124,3 @@ func FetchWarpMessageFromID(ethClient ethclient.Client, warpMessageID common.Has return NewWarpMessageInfo(logs[0]) } - -// The node serving the filter logs request may be behind the node serving the block header request, -// so we retry a few times to ensure we get the logs -func fetchWarpLogsWithRetries(ethClient ethclient.Client, blockNum *big.Int, numRetries int, retryInterval time.Duration) ([]types.Log, error) { - var ( - logs []types.Log - err error - ) - - for i := 0; i < numRetries; i++ { - logs, err = ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{ - Topics: [][]common.Hash{{WarpPrecompileLogFilter}, nil}, - Addresses: []common.Address{warp.ContractAddress}, - FromBlock: blockNum, - ToBlock: blockNum, - }) - if err == nil { - return logs, nil - } - if i != numRetries-1 { - time.Sleep(retryInterval) - } - } - return nil, fmt.Errorf("failed to fetch warp logs for block %d after %d retries: %w", blockNum.Uint64(), filterLogsRetries, err) -} From eb3e4a0be9eee3c89b897eb568b695eed7fe18d7 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 11 Jun 2024 12:39:40 -0400 Subject: [PATCH 08/56] lint --- main/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.go b/main/main.go index 1302033b..9930c60c 100644 --- a/main/main.go +++ b/main/main.go @@ -325,7 +325,7 @@ func createSourceClients( clients := make(map[ids.ID]ethclient.Client) for _, sourceBlockchain := range cfg.SourceBlockchains { - clients[sourceBlockchain.GetBlockchainID()], err = ethclient.DialWithConfig( + clients[sourceBlockchain.GetBlockchainID()], err = utils.DialWithConfig( ctx, sourceBlockchain.RPCEndpoint.BaseURL, sourceBlockchain.RPCEndpoint.HTTPHeaders, From d4042c678dfaaa77e8d713dd4595821ea19a1463 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 11 Jun 2024 16:44:05 -0400 Subject: [PATCH 09/56] Fix test --- main/main.go | 2 +- relayer/message_coordinator.go | 4 ++++ tests/relay_message_api.go | 16 +++++++--------- tests/utils/utils.go | 1 + types/types.go | 9 +++++---- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/main/main.go b/main/main.go index 9930c60c..3b2ae61a 100644 --- a/main/main.go +++ b/main/main.go @@ -214,7 +214,7 @@ func main() { logger.Fatal("Failed to create Application Relayers", zap.Error(err)) panic(err) } - relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers) + relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) // Initialize the API after the message coordinator is set http.Handle("/health", health.NewHandler(checker)) diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index c14127af..52db0367 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -33,11 +33,13 @@ func SetMessageCoordinator( logger logging.Logger, messageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory, applicationRelayers map[common.Hash]*ApplicationRelayer, + sourceClients map[ids.ID]ethclient.Client, ) { globalMessageCoordinator = &MessageCoordinator{ logger: logger, MessageHandlerFactories: messageHandlerFactories, ApplicationRelayers: applicationRelayers, + SourceClients: sourceClients, } } @@ -211,11 +213,13 @@ func ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.In func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { ethClient, ok := mc.SourceClients[blockchainID] if !ok { + mc.logger.Error("Source client not found", zap.String("blockchainID", blockchainID.String())) return fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) } warpMessage, err := relayerTypes.FetchWarpMessageFromID(ethClient, messageID, blockNum) if err != nil { + mc.logger.Error("Failed to fetch warp from blockchain", zap.String("blockchainID", blockchainID.String()), zap.Error(err)) return err } diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index ae52b7db..c0a53593 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -36,11 +36,8 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, fundedKey, relayerKey) log.Info("Sending teleporter messages") - receipt1, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) - warpMessage1 := getWarpMessageFromLog(ctx, receipt1, subnetAInfo) - receipt2, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) - warpMessage2 := getWarpMessageFromLog(ctx, receipt2, subnetAInfo) - warpMessage2.ID() + receipt, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) + warpMessage := getWarpMessageFromLog(ctx, receipt, subnetAInfo) // Set up relayer config relayerConfig := testUtils.CreateDefaultRelayerConfig( @@ -65,17 +62,18 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { reqBody := relayer.RelayMessageRequest{ BlockchainID: subnetAInfo.BlockchainID.String(), - MessageID: warpMessage1.ID().String(), - BlockNum: receipt1.BlockNumber.String(), + MessageID: warpMessage.ID().Hex(), + BlockNum: receipt.BlockNumber.String(), } b, err := json.Marshal(reqBody) Expect(err).Should(BeNil()) bodyReader := bytes.NewReader(b) - requestURL := fmt.Sprintf("http://localhost:%d", relayerConfig.APIPort) + requestURL := fmt.Sprintf("http://localhost:%d/relay-message", relayerConfig.APIPort) req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) Expect(err).Should(BeNil()) + req.Header.Set("Content-Type", "application/json") client := http.Client{ Timeout: 30 * time.Second, @@ -83,7 +81,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { res, err := client.Do(req) Expect(err).Should(BeNil()) - Expect(res.Status).Should(Equal(http.StatusOK)) + Expect(res.Status).Should(Equal("200 OK")) // Cancel the command and stop the relayer relayerCleanup() diff --git a/tests/utils/utils.go b/tests/utils/utils.go index 54b4bc6d..69aff8e7 100644 --- a/tests/utils/utils.go +++ b/tests/utils/utils.go @@ -203,6 +203,7 @@ func CreateDefaultRelayerConfig( ProcessMissedBlocks: false, SourceBlockchains: sources, DestinationBlockchains: destinations, + APIPort: 1234, } } diff --git a/types/types.go b/types/types.go index 8029b038..83f53777 100644 --- a/types/types.go +++ b/types/types.go @@ -6,6 +6,7 @@ package types import ( "context" "errors" + "fmt" "math/big" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" @@ -108,18 +109,18 @@ func UnpackWarpMessage(unsignedMsgBytes []byte) (*avalancheWarp.UnsignedMessage, return unsignedMsg, nil } -func FetchWarpMessageFromID(ethClient ethclient.Client, warpMessageID common.Hash, blockNum *big.Int) (*WarpMessageInfo, error) { +func FetchWarpMessageFromID(ethClient ethclient.Client, warpID common.Hash, blockNum *big.Int) (*WarpMessageInfo, error) { logs, err := ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{ - Topics: [][]common.Hash{{WarpPrecompileLogFilter}, nil, {warpMessageID}}, + Topics: [][]common.Hash{{WarpPrecompileLogFilter}, nil, {warpID}}, Addresses: []common.Address{warp.ContractAddress}, FromBlock: blockNum, ToBlock: blockNum, }) if err != nil { - return nil, err + return nil, fmt.Errorf("could not fetch logs: %w", err) } if len(logs) != 1 { - return nil, ErrInvalidLog + return nil, fmt.Errorf("found more than 1 log: %d", len(logs)) } return NewWarpMessageInfo(logs[0]) From 9081636e6986cbd2e7ec1d7662bc94a2c526996b Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 11 Jun 2024 19:58:25 -0400 Subject: [PATCH 10/56] Fix test --- tests/basic_relay.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/basic_relay.go b/tests/basic_relay.go index 0021a8ba..2be9c9bb 100644 --- a/tests/basic_relay.go +++ b/tests/basic_relay.go @@ -114,10 +114,8 @@ func BasicRelay(network interfaces.LocalNetwork) { relayerIDA := database.CalculateRelayerID(subnetAInfo.BlockchainID, subnetBInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress) relayerIDB := database.CalculateRelayerID(subnetBInfo.BlockchainID, subnetAInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress) // Modify the JSON database to force the relayer to re-process old blocks - err = jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0")) - Expect(err).Should(BeNil()) - err = jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0")) - Expect(err).Should(BeNil()) + _ = jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0")) + _ = jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0")) // Subscribe to the destination chain newHeadsB := make(chan *types.Header, 10) From 818f650b290371b06d470786350b3e71e55fe8a3 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 12 Jun 2024 11:22:50 -0400 Subject: [PATCH 11/56] small fixes --- main/main.go | 2 +- relayer/application_relayer.go | 4 +-- relayer/relay_message_api_handler.go | 9 ++++-- tests/relay_message_api.go | 45 ++++++++++++++++++++-------- 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/main/main.go b/main/main.go index 867c7ae9..b7c7e1a7 100644 --- a/main/main.go +++ b/main/main.go @@ -219,7 +219,7 @@ func main() { // Initialize the API after the message coordinator is set http.Handle("/health", health.NewHandler(checker)) - http.Handle("/relay-message", relayer.RelayMessageAPIHandler()) + http.Handle(relayer.RelayMessageApiPath, relayer.RelayMessageAPIHandler()) // start the health check server go func() { diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go index 31d0f78c..fa2ef4c5 100644 --- a/relayer/application_relayer.go +++ b/relayer/application_relayer.go @@ -167,13 +167,11 @@ func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) err reqID := r.currentRequestID r.lock.Unlock() - err := r.relayMessage( + return r.relayMessage( reqID, handler, true, ) - - return err } func (r *ApplicationRelayer) RelayerID() database.RelayerID { diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go index 71cb9f2b..334f537c 100644 --- a/relayer/relay_message_api_handler.go +++ b/relayer/relay_message_api_handler.go @@ -9,10 +9,15 @@ import ( "github.com/ethereum/go-ethereum/common" ) +const RelayMessageApiPath = "/relay-message" + type RelayMessageRequest struct { + // cb58 encoding of the blockchain ID BlockchainID string `json:"blockchain-id"` - MessageID string `json:"message-id"` - BlockNum string `json:"block-num"` + // Hex encoding of the warp message ID + MessageID string `json:"message-id"` + // Integer representation of the block number + BlockNum string `json:"block-num"` } func RelayMessageAPIHandler() http.HandlerFunc { diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index c0a53593..d39cfc59 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -35,7 +35,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { Expect(err).Should(BeNil()) testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, fundedKey, relayerKey) - log.Info("Sending teleporter messages") + log.Info("Sending teleporter message") receipt, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) warpMessage := getWarpMessageFromLog(ctx, receipt, subnetAInfo) @@ -66,22 +66,41 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { BlockNum: receipt.BlockNumber.String(), } - b, err := json.Marshal(reqBody) - Expect(err).Should(BeNil()) - bodyReader := bytes.NewReader(b) - - requestURL := fmt.Sprintf("http://localhost:%d/relay-message", relayerConfig.APIPort) - req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) - Expect(err).Should(BeNil()) - req.Header.Set("Content-Type", "application/json") - client := http.Client{ Timeout: 30 * time.Second, } - res, err := client.Do(req) - Expect(err).Should(BeNil()) - Expect(res.Status).Should(Equal("200 OK")) + requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, relayer.RelayMessageApiPath) + + // Send request to API + { + b, err := json.Marshal(reqBody) + Expect(err).Should(BeNil()) + bodyReader := bytes.NewReader(b) + + req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) + Expect(err).Should(BeNil()) + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + Expect(err).Should(BeNil()) + Expect(res.Status).Should(Equal("200 OK")) + } + + // Send the same request to ensure the correct response. + { + b, err := json.Marshal(reqBody) + Expect(err).Should(BeNil()) + bodyReader := bytes.NewReader(b) + + req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) + Expect(err).Should(BeNil()) + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + Expect(err).Should(BeNil()) + Expect(res.Status).Should(Equal("200 OK")) + } // Cancel the command and stop the relayer relayerCleanup() From 83259434dd194d620b449337866f5d5435f7f0bc Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Fri, 14 Jun 2024 10:48:01 -0400 Subject: [PATCH 12/56] Add error messages --- relayer/message_coordinator.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 52db0367..ca6d5511 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -220,7 +220,7 @@ func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID comm warpMessage, err := relayerTypes.FetchWarpMessageFromID(ethClient, messageID, blockNum) if err != nil { mc.logger.Error("Failed to fetch warp from blockchain", zap.String("blockchainID", blockchainID.String()), zap.Error(err)) - return err + return fmt.Errorf("could not fetch warp message from ID: %w", err) } appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) @@ -230,7 +230,7 @@ func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID comm zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), zap.Error(err), ) - return err + return fmt.Errorf("error getting application relayer: %w", err) } if appRelayer == nil { mc.logger.Error("Application relayer not found") From 23a9b2adaa7acee9439290223a24eb71b358a77a Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Fri, 14 Jun 2024 12:35:09 -0400 Subject: [PATCH 13/56] Update relayer/relay_message_api_handler.go Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Signed-off-by: Geoff Stuart --- relayer/relay_message_api_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go index 334f537c..1b8f5d24 100644 --- a/relayer/relay_message_api_handler.go +++ b/relayer/relay_message_api_handler.go @@ -32,7 +32,7 @@ func RelayMessageAPIHandler() http.HandlerFunc { blockchainID, err := ids.FromString(req.BlockchainID) if err != nil { - http.Error(w, "invalid blockchainID"+err.Error(), http.StatusBadRequest) + http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) return } messageID := common.HexToHash(req.MessageID) From 6dc2441bb03dc49a9e1be1f5c768c913dda73254 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 18 Jun 2024 12:31:05 -0400 Subject: [PATCH 14/56] Review fixes --- main/main.go | 2 +- relayer/listener.go | 36 ++++++++-------- relayer/relay_message_api_handler.go | 64 ++++++++++++++-------------- 3 files changed, 49 insertions(+), 53 deletions(-) diff --git a/main/main.go b/main/main.go index b7c7e1a7..ce816564 100644 --- a/main/main.go +++ b/main/main.go @@ -219,7 +219,7 @@ func main() { // Initialize the API after the message coordinator is set http.Handle("/health", health.NewHandler(checker)) - http.Handle(relayer.RelayMessageApiPath, relayer.RelayMessageAPIHandler()) + http.HandleFunc(relayer.RelayMessageApiPath, relayer.RelayMessageAPIHandler) // start the health check server go func() { diff --git a/relayer/listener.go b/relayer/listener.go index d422efd2..da722248 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -28,15 +28,14 @@ const ( // Listener handles all messages sent from a given source chain type Listener struct { - Subscriber vms.Subscriber - currentRequestID uint32 - contractMessage vms.ContractMessage - logger logging.Logger - sourceBlockchain config.SourceBlockchain - catchUpResultChan chan bool - healthStatus *atomic.Bool - processMissedBlocks bool - ethClient ethclient.Client + Subscriber vms.Subscriber + currentRequestID uint32 + contractMessage vms.ContractMessage + logger logging.Logger + sourceBlockchain config.SourceBlockchain + catchUpResultChan chan bool + healthStatus *atomic.Bool + ethClient ethclient.Client } // runListener creates a Listener instance and the ApplicationRelayers for a subnet. @@ -123,15 +122,14 @@ func newListener( zap.String("blockchainIDHex", sourceBlockchain.GetBlockchainID().Hex()), ) lstnr := Listener{ - Subscriber: sub, - currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision - contractMessage: vms.NewContractMessage(logger, sourceBlockchain), - logger: logger, - sourceBlockchain: sourceBlockchain, - catchUpResultChan: catchUpResultChan, - healthStatus: relayerHealth, - processMissedBlocks: processMissedBlocks, - ethClient: ethRPCClient, + Subscriber: sub, + currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision + contractMessage: vms.NewContractMessage(logger, sourceBlockchain), + logger: logger, + sourceBlockchain: sourceBlockchain, + catchUpResultChan: catchUpResultChan, + healthStatus: relayerHealth, + ethClient: ethRPCClient, } // Open the subscription. We must do this before processing any missed messages, otherwise we may miss an incoming message @@ -145,7 +143,7 @@ func newListener( return nil, err } - if lstnr.processMissedBlocks { + if processMissedBlocks { // 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. diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go index 1b8f5d24..5c1d5a43 100644 --- a/relayer/relay_message_api_handler.go +++ b/relayer/relay_message_api_handler.go @@ -9,45 +9,43 @@ import ( "github.com/ethereum/go-ethereum/common" ) -const RelayMessageApiPath = "/relay-message" +const RelayMessageApiPath = "/relay" type RelayMessageRequest struct { - // cb58 encoding of the blockchain ID + // Required. cb58 encoding of the blockchain ID BlockchainID string `json:"blockchain-id"` - // Hex encoding of the warp message ID + // Required. Hex encoding of the warp message ID MessageID string `json:"message-id"` - // Integer representation of the block number + // Required. Integer representation of the block number BlockNum string `json:"block-num"` } -func RelayMessageAPIHandler() http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - var req RelayMessageRequest - - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - blockchainID, err := ids.FromString(req.BlockchainID) - if err != nil { - http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) - return - } - messageID := common.HexToHash(req.MessageID) - blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) - if !ok { - http.Error(w, "invalid blockNum", http.StatusBadRequest) - return - } - - err = ProcessMessage(blockchainID, messageID, blockNum) - if err != nil { - http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) - return - } - - _, _ = w.Write([]byte("Message processed successfully")) +func RelayMessageAPIHandler(w http.ResponseWriter, r *http.Request) { + var req RelayMessageRequest + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + blockchainID, err := ids.FromString(req.BlockchainID) + if err != nil { + http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) + return + } + messageID := common.HexToHash(req.MessageID) + blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) + if !ok { + http.Error(w, "invalid blockNum", http.StatusBadRequest) + return } + + err = ProcessMessage(blockchainID, messageID, blockNum) + if err != nil { + http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte("Message processed successfully")) } From 177c15fc5b7875e6221bca00315f444a179cc42e Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 18 Jun 2024 15:25:45 -0400 Subject: [PATCH 15/56] Update main/main.go Co-authored-by: minghinmatthewlam Signed-off-by: Geoff Stuart --- main/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.go b/main/main.go index ce816564..bfeb0917 100644 --- a/main/main.go +++ b/main/main.go @@ -111,7 +111,7 @@ func main() { } // Initialize all source clients - logger.Info("Initializing destination clients") + logger.Info("Initializing source clients") sourceClients, err := createSourceClients(context.Background(), logger, &cfg) if err != nil { logger.Fatal("Failed to create source clients", zap.Error(err)) From cf3c779dece76c20f31431ed3bdf9e20d7527c48 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 18 Jun 2024 15:26:26 -0400 Subject: [PATCH 16/56] Update main/main.go Co-authored-by: minghinmatthewlam Signed-off-by: Geoff Stuart --- main/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.go b/main/main.go index bfeb0917..66c226f7 100644 --- a/main/main.go +++ b/main/main.go @@ -195,7 +195,7 @@ func main() { messageHandlerFactories, err := createMessageHandlerFactories(logger, &cfg) if err != nil { - logger.Fatal("Failed to create Message Handler Factories", zap.Error(err)) + logger.Fatal("Failed to create message handler factories", zap.Error(err)) panic(err) } From 664cd310d106ce35f8615d903f80529a840e753b Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 18 Jun 2024 15:27:54 -0400 Subject: [PATCH 17/56] Update main/main.go Co-authored-by: minghinmatthewlam Signed-off-by: Geoff Stuart --- main/main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main/main.go b/main/main.go index 66c226f7..920a8969 100644 --- a/main/main.go +++ b/main/main.go @@ -212,7 +212,8 @@ func main() { destinationClients, ) if err != nil { - logger.Fatal("Failed to create Application Relayers", zap.Error(err)) + logger.Fatal("Failed to create application relayers", zap.Error(err)) +`` panic(err) } relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) From 0e650395c7f10ed137cccb038d906ab297f2772e Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 18 Jun 2024 15:28:03 -0400 Subject: [PATCH 18/56] Update main/main.go Co-authored-by: minghinmatthewlam Signed-off-by: Geoff Stuart --- main/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.go b/main/main.go index 920a8969..e164bb96 100644 --- a/main/main.go +++ b/main/main.go @@ -308,7 +308,7 @@ func createMessageHandlerFactories( m, err = nil, fmt.Errorf("invalid message format %s", format) } if err != nil { - logger.Error("Failed to create message manager", zap.Error(err)) + logger.Error("Failed to create message handler factory", zap.Error(err)) return nil, err } messageHandlerFactoriesForSource[address] = m From fec26c654112348e998e70fbc5402d4763cc3a1f Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 18 Jun 2024 15:29:44 -0400 Subject: [PATCH 19/56] Fix typo --- main/main.go | 1 - 1 file changed, 1 deletion(-) diff --git a/main/main.go b/main/main.go index e164bb96..f8df4f80 100644 --- a/main/main.go +++ b/main/main.go @@ -213,7 +213,6 @@ func main() { ) if err != nil { logger.Fatal("Failed to create application relayers", zap.Error(err)) -`` panic(err) } relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) From 56f4c384878f3945d3654102b93caee93f1ef74e Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 19 Jun 2024 13:19:35 -0400 Subject: [PATCH 20/56] Add txhash to SendMessage --- messages/message_handler.go | 2 +- .../off-chain-registry/message_handler.go | 10 ++++----- messages/teleporter/message_handler.go | 16 +++++++------- relayer/application_relayer.go | 22 ++++++++++--------- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/messages/message_handler.go b/messages/message_handler.go index 1ebfc04c..428a8fd1 100644 --- a/messages/message_handler.go +++ b/messages/message_handler.go @@ -27,7 +27,7 @@ type MessageHandler interface { // 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 + SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) (common.Hash, error) // GetMessageRoutingInfo returns the source chain ID, origin sender address, destination chain ID, and destination address GetMessageRoutingInfo() ( diff --git a/messages/off-chain-registry/message_handler.go b/messages/off-chain-registry/message_handler.go index 8abd17e4..bf6ca351 100644 --- a/messages/off-chain-registry/message_handler.go +++ b/messages/off-chain-registry/message_handler.go @@ -144,7 +144,7 @@ func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClie return false, nil } -func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) error { +func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) (common.Hash, 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) @@ -154,10 +154,10 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()), zap.String("warpMessageID", signedMessage.ID().String()), ) - return err + return common.Hash{}, err } - _, err = destinationClient.SendTx(signedMessage, m.factory.registryAddress.Hex(), addProtocolVersionGasLimit, callData) + txHash, err := destinationClient.SendTx(signedMessage, m.factory.registryAddress.Hex(), addProtocolVersionGasLimit, callData) if err != nil { m.logger.Error( "Failed to send tx.", @@ -165,14 +165,14 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli zap.String("warpMessageID", signedMessage.ID().String()), zap.Error(err), ) - return err + return common.Hash{}, err } m.logger.Info( "Sent message to destination chain", zap.String("destinationBlockchainID", destinationClient.DestinationBlockchainID().String()), zap.String("warpMessageID", signedMessage.ID().String()), ) - return nil + return txHash, nil } func (m *messageHandler) GetMessageRoutingInfo() ( diff --git a/messages/teleporter/message_handler.go b/messages/teleporter/message_handler.go index 806d254a..0e58e044 100644 --- a/messages/teleporter/message_handler.go +++ b/messages/teleporter/message_handler.go @@ -172,7 +172,7 @@ func (m *messageHandler) ShouldSendMessage(destinationClient vms.DestinationClie // 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 *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) error { +func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) (common.Hash, error) { destinationBlockchainID := destinationClient.DestinationBlockchainID() teleporterMessageID, err := teleporterUtils.CalculateMessageID( m.factory.protocolAddress, @@ -181,7 +181,7 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli m.teleporterMessage.MessageNonce, ) if err != nil { - return fmt.Errorf("failed to calculate Teleporter message ID: %w", err) + return common.Hash{}, fmt.Errorf("failed to calculate Teleporter message ID: %w", err) } m.logger.Info( @@ -198,7 +198,7 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli zap.String("warpMessageID", signedMessage.ID().String()), zap.String("teleporterMessageID", teleporterMessageID.String()), ) - return err + return common.Hash{}, err } gasLimit, err := gasUtils.CalculateReceiveMessageGasLimit( @@ -215,7 +215,7 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli zap.String("warpMessageID", signedMessage.ID().String()), zap.String("teleporterMessageID", teleporterMessageID.String()), ) - return err + return common.Hash{}, 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.factory.messageConfig.RewardAddress)) @@ -226,7 +226,7 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli zap.String("warpMessageID", signedMessage.ID().String()), zap.String("teleporterMessageID", teleporterMessageID.String()), ) - return err + return common.Hash{}, err } txHash, err := destinationClient.SendTx(signedMessage, m.factory.protocolAddress.Hex(), gasLimit, callData) @@ -238,13 +238,13 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli zap.String("teleporterMessageID", teleporterMessageID.String()), zap.Error(err), ) - return err + return common.Hash{}, err } // Wait for the message to be included in a block before returning err = m.waitForReceipt(signedMessage, destinationClient, txHash, teleporterMessageID) if err != nil { - return err + return common.Hash{}, err } m.logger.Info( @@ -254,7 +254,7 @@ func (m *messageHandler) SendMessage(signedMessage *warp.Message, destinationCli zap.String("teleporterMessageID", teleporterMessageID.String()), zap.String("txHash", txHash.String()), ) - return nil + return txHash, nil } func (m *messageHandler) waitForReceipt(signedMessage *warp.Message, destinationClient vms.DestinationClient, txHash common.Hash, teleporterMessageID ids.ID) error { diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go index 356f3e54..1755fd8d 100644 --- a/relayer/application_relayer.go +++ b/relayer/application_relayer.go @@ -31,6 +31,7 @@ import ( 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" + "github.com/ethereum/go-ethereum/common" "golang.org/x/sync/errgroup" "go.uber.org/zap" @@ -136,7 +137,8 @@ func (r *ApplicationRelayer) ProcessHeight(height uint64, handlers []messages.Me // 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) + _, err := r.ProcessMessage(h) + return err }) } if err := eg.Wait(); err != nil { @@ -160,7 +162,7 @@ func (r *ApplicationRelayer) ProcessHeight(height uint64, handlers []messages.Me } // Relays a message to the destination chain. Does not checkpoint the height. -func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) error { +func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) (common.Hash, error) { // Increment the request ID. Make sure we don't hold the lock while we relay the message. r.lock.Lock() r.currentRequestID++ @@ -182,7 +184,7 @@ func (r *ApplicationRelayer) relayMessage( requestID uint32, handler messages.MessageHandler, useAppRequestNetwork bool, -) error { +) (common.Hash, error) { r.logger.Debug( "Relaying message", zap.Uint32("requestID", requestID), @@ -196,11 +198,11 @@ func (r *ApplicationRelayer) relayMessage( zap.Error(err), ) r.incFailedRelayMessageCount("failed to check if message should be sent") - return err + return common.Hash{}, err } if !shouldSend { r.logger.Info("Message should not be sent") - return nil + return common.Hash{}, nil } unsignedMessage := handler.GetUnsignedMessage() @@ -215,7 +217,7 @@ func (r *ApplicationRelayer) relayMessage( zap.Error(err), ) r.incFailedRelayMessageCount("failed to create signed warp message via AppRequest network") - return err + return common.Hash{}, err } } else { signedMessage, err = r.createSignedMessage(unsignedMessage) @@ -225,21 +227,21 @@ func (r *ApplicationRelayer) relayMessage( zap.Error(err), ) r.incFailedRelayMessageCount("failed to create signed warp message via RPC") - return err + return common.Hash{}, err } } // create signed message latency (ms) r.setCreateSignedMessageLatencyMS(float64(time.Since(startCreateSignedMessageTime).Milliseconds())) - err = handler.SendMessage(signedMessage, r.destinationClient) + txHash, err := handler.SendMessage(signedMessage, r.destinationClient) if err != nil { r.logger.Error( "Failed to send warp message", zap.Error(err), ) r.incFailedRelayMessageCount("failed to send warp message") - return err + return common.Hash{}, err } r.logger.Info( "Finished relaying message to destination chain", @@ -247,7 +249,7 @@ func (r *ApplicationRelayer) relayMessage( ) r.incSuccessfulRelayMessageCount() - return nil + return txHash, nil } // createSignedMessage fetches the signed Warp message from the source chain via RPC. From 78632811a6595f7e2e99ddfda90b5ae1f39c1eb2 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 20 Jun 2024 11:40:52 -0400 Subject: [PATCH 21/56] Fix --- relayer/message_coordinator.go | 14 +++++++------- relayer/relay_message_api_handler.go | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index ca6d5511..9d1e4d84 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -190,7 +190,7 @@ func (mc *MessageCoordinator) processManualWarpMessages( ) return err } - err = appRelayer.ProcessMessage(handler) + _, err = appRelayer.ProcessMessage(handler) if err != nil { mc.logger.Error( "Failed to process manual Warp message", @@ -203,24 +203,24 @@ func (mc *MessageCoordinator) processManualWarpMessages( return nil } -func ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { +func ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { if globalMessageCoordinator == nil { panic("global message coordinator not set") } return globalMessageCoordinator.processMessage(blockchainID, messageID, blockNum) } -func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) error { +func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { ethClient, ok := mc.SourceClients[blockchainID] if !ok { mc.logger.Error("Source client not found", zap.String("blockchainID", blockchainID.String())) - return fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) + return common.Hash{}, fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) } warpMessage, err := relayerTypes.FetchWarpMessageFromID(ethClient, messageID, blockNum) if err != nil { mc.logger.Error("Failed to fetch warp from blockchain", zap.String("blockchainID", blockchainID.String()), zap.Error(err)) - return fmt.Errorf("could not fetch warp message from ID: %w", err) + return common.Hash{}, fmt.Errorf("could not fetch warp message from ID: %w", err) } appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) @@ -230,11 +230,11 @@ func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID comm zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), zap.Error(err), ) - return fmt.Errorf("error getting application relayer: %w", err) + return common.Hash{}, fmt.Errorf("error getting application relayer: %w", err) } if appRelayer == nil { mc.logger.Error("Application relayer not found") - return errors.New("application relayer not found") + return common.Hash{}, errors.New("application relayer not found") } return appRelayer.ProcessMessage(handler) diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go index 5c1d5a43..eeada70d 100644 --- a/relayer/relay_message_api_handler.go +++ b/relayer/relay_message_api_handler.go @@ -41,11 +41,11 @@ func RelayMessageAPIHandler(w http.ResponseWriter, r *http.Request) { return } - err = ProcessMessage(blockchainID, messageID, blockNum) + txHash, err := ProcessMessage(blockchainID, messageID, blockNum) if err != nil { http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) return } - _, _ = w.Write([]byte("Message processed successfully")) + _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) } From 039e5d72f9953cf8f6519a6da1f686ca0b552863 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 20 Jun 2024 12:29:20 -0400 Subject: [PATCH 22/56] Add endpoint for manual warp messages --- config/config.go | 10 +-- config/manual_warp_message.go | 76 ---------------- main/main.go | 27 ------ relayer/manual_warp_message.go | 2 + relayer/message_coordinator.go | 48 ++++------ relayer/relay_message_api_handler.go | 46 +++++++++- tests/e2e_test.go | 3 - tests/manual_message.go | 129 --------------------------- tests/relay_message_api.go | 24 +++++ tests/teleporter_registry.go | 59 +++++++----- 10 files changed, 129 insertions(+), 295 deletions(-) delete mode 100644 config/manual_warp_message.go create mode 100644 relayer/manual_warp_message.go delete mode 100644 tests/manual_message.go diff --git a/config/config.go b/config/config.go index 4ae1fe00..fc4bc4c5 100644 --- a/config/config.go +++ b/config/config.go @@ -58,7 +58,6 @@ type Config struct { SourceBlockchains []*SourceBlockchain `mapstructure:"source-blockchains" json:"source-blockchains"` DestinationBlockchains []*DestinationBlockchain `mapstructure:"destination-blockchains" json:"destination-blockchains"` ProcessMissedBlocks bool `mapstructure:"process-missed-blocks" json:"process-missed-blocks"` - ManualWarpMessages []*ManualWarpMessage `mapstructure:"manual-warp-messages" json:"manual-warp-messages"` // convenience field to fetch a blockchain's subnet ID blockchainIDToSubnetID map[ids.ID]ids.ID @@ -119,14 +118,7 @@ func (c *Config) Validate() error { blockchainIDToSubnetID[s.blockchainID] = s.subnetID } c.blockchainIDToSubnetID = blockchainIDToSubnetID - - // Validate the manual warp messages - for i, msg := range c.ManualWarpMessages { - if err := msg.Validate(); err != nil { - return fmt.Errorf("invalid manual warp message at index %d: %w", i, err) - } - } - + return nil } diff --git a/config/manual_warp_message.go b/config/manual_warp_message.go deleted file mode 100644 index db5b26b0..00000000 --- a/config/manual_warp_message.go +++ /dev/null @@ -1,76 +0,0 @@ -package config - -import ( - "encoding/hex" - "errors" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/awm-relayer/utils" - "github.com/ethereum/go-ethereum/common" -) - -// Defines a manual warp message to be sent from the relayer on startup. -type ManualWarpMessage struct { - UnsignedMessageBytes string `mapstructure:"unsigned-message-bytes" json:"unsigned-message-bytes"` - SourceBlockchainID string `mapstructure:"source-blockchain-id" json:"source-blockchain-id"` - DestinationBlockchainID string `mapstructure:"destination-blockchain-id" json:"destination-blockchain-id"` - SourceAddress string `mapstructure:"source-address" json:"source-address"` - DestinationAddress string `mapstructure:"destination-address" json:"destination-address"` - - // convenience fields to access the values after initialization - unsignedMessageBytes []byte - sourceBlockchainID ids.ID - destinationBlockchainID ids.ID - sourceAddress common.Address - destinationAddress common.Address -} - -// Validates the manual Warp message configuration. -// Does not modify the public fields as derived from the configuration passed to the application, -// but does initialize private fields available through getters -func (m *ManualWarpMessage) Validate() error { - unsignedMsg, err := hex.DecodeString(utils.SanitizeHexString(m.UnsignedMessageBytes)) - if err != nil { - return err - } - sourceBlockchainID, err := ids.FromString(m.SourceBlockchainID) - if err != nil { - return err - } - if !common.IsHexAddress(m.SourceAddress) { - return errors.New("invalid source address in manual warp message configuration") - } - destinationBlockchainID, err := ids.FromString(m.DestinationBlockchainID) - if err != nil { - return err - } - if !common.IsHexAddress(m.DestinationAddress) { - return errors.New("invalid destination address in manual warp message configuration") - } - m.unsignedMessageBytes = unsignedMsg - m.sourceBlockchainID = sourceBlockchainID - m.sourceAddress = common.HexToAddress(m.SourceAddress) - m.destinationBlockchainID = destinationBlockchainID - m.destinationAddress = common.HexToAddress(m.DestinationAddress) - return nil -} - -func (m *ManualWarpMessage) GetUnsignedMessageBytes() []byte { - return m.unsignedMessageBytes -} - -func (m *ManualWarpMessage) GetSourceBlockchainID() ids.ID { - return m.sourceBlockchainID -} - -func (m *ManualWarpMessage) GetSourceAddress() common.Address { - return m.sourceAddress -} - -func (m *ManualWarpMessage) GetDestinationBlockchainID() ids.ID { - return m.destinationBlockchainID -} - -func (m *ManualWarpMessage) GetDestinationAddress() common.Address { - return m.destinationAddress -} diff --git a/main/main.go b/main/main.go index f8df4f80..09c98658 100644 --- a/main/main.go +++ b/main/main.go @@ -5,7 +5,6 @@ package main import ( "context" - "encoding/hex" "fmt" "log" "net/http" @@ -26,8 +25,6 @@ import ( "github.com/ava-labs/awm-relayer/database" "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/ava-labs/subnet-evm/ethclient" @@ -226,26 +223,6 @@ func main() { log.Fatalln(http.ListenAndServe(fmt.Sprintf(":%d", cfg.APIPort), nil)) }() - // Gather manual Warp messages specified in the configuration - manualWarpMessages := make(map[ids.ID][]*relayerTypes.WarpMessageInfo) - for _, msg := range cfg.ManualWarpMessages { - sourceBlockchainID := msg.GetSourceBlockchainID() - unsignedMsg, err := types.UnpackWarpMessage(msg.GetUnsignedMessageBytes()) - if err != nil { - logger.Fatal( - "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 listeners for each of the subnets configured as a source errGroup, ctx := errgroup.WithContext(context.Background()) for _, s := range cfg.SourceBlockchains { @@ -254,10 +231,6 @@ func main() { isHealthy := atomic.NewBool(true) relayerHealth[s.GetBlockchainID()] = isHealthy - errGroup.Go(func() error { - return relayer.ProcessManualWarpMessages(manualWarpMessages[sourceBlockchain.GetBlockchainID()]) - }) - // errgroup will cancel the context when the first goroutine returns an error errGroup.Go(func() error { // runListener runs until it errors or the context is cancelled by another goroutine diff --git a/relayer/manual_warp_message.go b/relayer/manual_warp_message.go new file mode 100644 index 00000000..7848e514 --- /dev/null +++ b/relayer/manual_warp_message.go @@ -0,0 +1,2 @@ +package relayer + diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 9d1e4d84..d9d71eb6 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -164,43 +164,33 @@ func (mc *MessageCoordinator) getApplicationRelayer( return nil } -func ProcessManualWarpMessages(manualWarpMessages []*relayerTypes.WarpMessageInfo) error { +func ProcessManualWarpMessage(warpMessage *relayerTypes.WarpMessageInfo) (common.Hash, error) { if globalMessageCoordinator == nil { - return fmt.Errorf("global message coordinator not set") + return common.Hash{}, fmt.Errorf("global message coordinator not set") } - return globalMessageCoordinator.processManualWarpMessages(manualWarpMessages) + return globalMessageCoordinator.processManualWarpMessage(warpMessage) } -func (mc *MessageCoordinator) processManualWarpMessages( - manualWarpMessages []*relayerTypes.WarpMessageInfo, -) error { +func (mc *MessageCoordinator) processManualWarpMessage( + warpMessage *relayerTypes.WarpMessageInfo, +) (common.Hash, error) { // Send any messages that were specified in the configuration - for _, warpMessage := range manualWarpMessages { - mc.logger.Info( - "Relaying manual Warp message", - zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), + mc.logger.Info( + "Relaying manual Warp message", + zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), + zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), + ) + appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) + if err != nil { + mc.logger.Error( + "Failed to parse manual Warp message.", + zap.Error(err), zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), ) - appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) - if err != nil { - mc.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 { - mc.logger.Error( - "Failed to process manual Warp message", - zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), - zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), - ) - return err - } + return common.Hash{}, err } - return nil + + return appRelayer.ProcessMessage(handler) } func ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go index eeada70d..5acb6fda 100644 --- a/relayer/relay_message_api_handler.go +++ b/relayer/relay_message_api_handler.go @@ -5,11 +5,17 @@ import ( "math/big" "net/http" + "github.com/ava-labs/awm-relayer/types" + relayerTypes "github.com/ava-labs/awm-relayer/types" + "github.com/ava-labs/avalanchego/ids" "github.com/ethereum/go-ethereum/common" ) -const RelayMessageApiPath = "/relay" +const ( + RelayApiPath = "/relay" + RelayMessageApiPath = RelayApiPath + "/message" +) type RelayMessageRequest struct { // Required. cb58 encoding of the blockchain ID @@ -20,6 +26,44 @@ type RelayMessageRequest struct { BlockNum string `json:"block-num"` } +// Defines a manual warp message to be sent from the relayer through the API. +type ManualWarpMessage struct { + UnsignedMessageBytes []byte + SourceBlockchainID ids.ID + DestinationBlockchainID ids.ID + SourceAddress common.Address + DestinationAddress common.Address +} + +func RelayAPIHandler(w http.ResponseWriter, r *http.Request) { + var req ManualWarpMessage + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + warpMessageInfo := &relayerTypes.WarpMessageInfo{ + SourceAddress: req.SourceAddress, + UnsignedMessage: unsignedMessage, + } + + txHash, err := ProcessManualWarpMessage(warpMessageInfo) + if err != nil { + http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) +} + func RelayMessageAPIHandler(w http.ResponseWriter, r *http.Request) { var req RelayMessageRequest diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 7973893f..b9af3b8f 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -68,9 +68,6 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { ginkgo.It("Basic Relay", func() { BasicRelay(localNetworkInstance) }) - ginkgo.It("Teleporter Registry", func() { - TeleporterRegistry(localNetworkInstance) - }) ginkgo.It("Shared Database", func() { SharedDatabaseAccess(localNetworkInstance) }) diff --git a/tests/manual_message.go b/tests/manual_message.go deleted file mode 100644 index 9a487d98..00000000 --- a/tests/manual_message.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package tests - -import ( - "context" - "encoding/hex" - "time" - - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" - "github.com/ava-labs/awm-relayer/config" - 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" - subnetEvmInterfaces "github.com/ava-labs/subnet-evm/interfaces" - "github.com/ava-labs/subnet-evm/precompile/contracts/warp" - "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" -) - -// This tests relaying a message manually provided in the relayer config -func ManualMessage(network interfaces.LocalNetwork) { - subnetAInfo := network.GetPrimaryNetworkInfo() - subnetBInfo, _ := utils.GetTwoSubnets(network) - fundedAddress, fundedKey := network.GetFundedAccountInfo() - teleporterContractAddress := network.GetTeleporterContractAddress() - err := testUtils.ClearRelayerStorage() - Expect(err).Should(BeNil()) - - // - // Fund the relayer address on all subnets - // - ctx := context.Background() - - 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) - - // - // Send two Teleporter message on Subnet A, before the relayer is running - // - - log.Info("Sending two teleporter messages on subnet A") - // This message will be delivered by the relayer - receipt1, _, id1 := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) - msg1 := getWarpMessageFromLog(ctx, receipt1, subnetAInfo) - - // This message will not be delivered by the relayer - _, _, id2 := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) - - // - // Set up relayer config to deliver one of the two previously sent messages - // - relayerConfig := testUtils.CreateDefaultRelayerConfig( - []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, - []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, - teleporterContractAddress, - fundedAddress, - relayerKey, - ) - relayerConfig.ManualWarpMessages = []*config.ManualWarpMessage{ - { - UnsignedMessageBytes: hex.EncodeToString(msg1.Bytes()), - SourceBlockchainID: subnetAInfo.BlockchainID.String(), - DestinationBlockchainID: subnetBInfo.BlockchainID.String(), - SourceAddress: teleporterContractAddress.Hex(), - DestinationAddress: teleporterContractAddress.Hex(), - }, - } - - relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname) - - // - // Run the Relayer. On startup, we should deliver the message provided in the config - // - - // Subscribe to the destination chain - newHeadsB := make(chan *types.Header, 10) - sub, err := subnetBInfo.WSClient.SubscribeNewHead(ctx, newHeadsB) - Expect(err).Should(BeNil()) - defer sub.Unsubscribe() - - log.Info("Starting the relayer") - relayerCleanup := testUtils.BuildAndRunRelayerExecutable(ctx, relayerConfigPath) - defer relayerCleanup() - - log.Info("Waiting for a new block confirmation on subnet B") - <-newHeadsB - delivered1, err := subnetBInfo.TeleporterMessenger.MessageReceived( - &bind.CallOpts{}, id1, - ) - Expect(err).Should(BeNil()) - Expect(delivered1).Should(BeTrue()) - - log.Info("Waiting for 10s to ensure no new block confirmations on destination chain") - Consistently(newHeadsB, 10*time.Second, 500*time.Millisecond).ShouldNot(Receive()) - - delivered2, err := subnetBInfo.TeleporterMessenger.MessageReceived( - &bind.CallOpts{}, id2, - ) - Expect(err).Should(BeNil()) - Expect(delivered2).Should(BeFalse()) -} - -func getWarpMessageFromLog(ctx context.Context, receipt *types.Receipt, source interfaces.SubnetTestInfo) *avalancheWarp.UnsignedMessage { - log.Info("Fetching relevant warp logs from the newly produced block") - logs, err := source.RPCClient.FilterLogs(ctx, subnetEvmInterfaces.FilterQuery{ - BlockHash: &receipt.BlockHash, - Addresses: []common.Address{warp.Module.Address}, - }) - Expect(err).Should(BeNil()) - Expect(len(logs)).Should(Equal(1)) - - // Check for relevant warp log from subscription and ensure that it matches - // the log extracted from the last block. - txLog := logs[0] - log.Info("Parsing logData as unsigned warp message") - unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data) - Expect(err).Should(BeNil()) - - return unsignedMsg -} diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index d39cfc59..b7d34327 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -14,10 +14,15 @@ import ( "github.com/ava-labs/awm-relayer/relayer" testUtils "github.com/ava-labs/awm-relayer/tests/utils" + "github.com/ava-labs/subnet-evm/core/types" + subnetEvmInterfaces "github.com/ava-labs/subnet-evm/interfaces" + "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "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" + avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" . "github.com/onsi/gomega" ) @@ -105,3 +110,22 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { // Cancel the command and stop the relayer relayerCleanup() } + +func getWarpMessageFromLog(ctx context.Context, receipt *types.Receipt, source interfaces.SubnetTestInfo) *avalancheWarp.UnsignedMessage { + log.Info("Fetching relevant warp logs from the newly produced block") + logs, err := source.RPCClient.FilterLogs(ctx, subnetEvmInterfaces.FilterQuery{ + BlockHash: &receipt.BlockHash, + Addresses: []common.Address{warp.Module.Address}, + }) + Expect(err).Should(BeNil()) + Expect(len(logs)).Should(Equal(1)) + + // Check for relevant warp log from subscription and ensure that it matches + // the log extracted from the last block. + txLog := logs[0] + log.Info("Parsing logData as unsigned warp message") + unsignedMsg, err := warp.UnpackSendWarpEventDataToMessage(txLog.Data) + Expect(err).Should(BeNil()) + + return unsignedMsg +} diff --git a/tests/teleporter_registry.go b/tests/teleporter_registry.go index 13c4698e..de61079b 100644 --- a/tests/teleporter_registry.go +++ b/tests/teleporter_registry.go @@ -4,13 +4,17 @@ package tests import ( + "bytes" "context" - "encoding/hex" + "encoding/json" + "fmt" "math/big" + "net/http" + "time" runner_sdk "github.com/ava-labs/avalanche-network-runner/client" - "github.com/ava-labs/awm-relayer/config" offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" + "github.com/ava-labs/awm-relayer/relayer" 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" @@ -25,7 +29,7 @@ import ( // Tests relayer support for off-chain Teleporter Registry updates // - Configures the relayer to send an off-chain message to the Teleporter Registry // - Verifies that the Teleporter Registry is updated -func TeleporterRegistry(network interfaces.LocalNetwork) { +func ManualMessage(network interfaces.LocalNetwork) { cChainInfo := network.GetPrimaryNetworkInfo() subnetAInfo, subnetBInfo := teleporterTestUtils.GetTwoSubnets(network) fundedAddress, fundedKey := network.GetFundedAccountInfo() @@ -88,19 +92,7 @@ func TeleporterRegistry(network interfaces.LocalNetwork) { fundedAddress, relayerKey, ) - relayerConfig.ManualWarpMessages = []*config.ManualWarpMessage{ - { - UnsignedMessageBytes: hex.EncodeToString(unsignedMessage.Bytes()), - SourceBlockchainID: cChainInfo.BlockchainID.String(), - DestinationBlockchainID: cChainInfo.BlockchainID.String(), - SourceAddress: offchainregistry.OffChainRegistrySourceAddress.Hex(), - DestinationAddress: cChainInfo.TeleporterRegistryAddress.Hex(), - }, - } relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname) - // - // Run the Relayer. On startup, we should deliver the message provided in the config - // // Subscribe to the destination chain newHeadsC := make(chan *types.Header, 10) @@ -112,11 +104,36 @@ func TeleporterRegistry(network interfaces.LocalNetwork) { relayerCleanup := testUtils.BuildAndRunRelayerExecutable(ctx, relayerConfigPath) defer relayerCleanup() - log.Info("Waiting for a new block confirmation on the C-Chain") - <-newHeadsC + reqBody := relayer.ManualWarpMessage{ + UnsignedMessageBytes: unsignedMessage.Bytes(), + SourceBlockchainID: cChainInfo.BlockchainID, + DestinationBlockchainID: cChainInfo.BlockchainID, + SourceAddress: offchainregistry.OffChainRegistrySourceAddress, + DestinationAddress: cChainInfo.TeleporterRegistryAddress, + } - log.Info("Verifying that the Teleporter Registry was updated") - newVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{}) - Expect(err).Should(BeNil()) - Expect(newVersion.Cmp(expectedNewVersion)).Should(Equal(0)) + client := http.Client{ + Timeout: 30 * time.Second, + } + + requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, relayer.RelayMessageApiPath) + + // Send request to API + { + b, err := json.Marshal(reqBody) + Expect(err).Should(BeNil()) + bodyReader := bytes.NewReader(b) + + req, err := http.NewRequest(http.MethodPost, requestURL, bodyReader) + Expect(err).Should(BeNil()) + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + Expect(err).Should(BeNil()) + Expect(res.Status).Should(Equal("200 OK")) + + newVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{}) + Expect(err).Should(BeNil()) + Expect(newVersion.Cmp(expectedNewVersion)).Should(Equal(0)) + } } From e72a34b93d7b09379b6f893c9f5a059c0034a691 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 20 Jun 2024 12:34:36 -0400 Subject: [PATCH 23/56] lint --- config/config.go | 2 +- relayer/manual_warp_message.go | 2 -- tests/relay_message_api.go | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) delete mode 100644 relayer/manual_warp_message.go diff --git a/config/config.go b/config/config.go index fc4bc4c5..590c7527 100644 --- a/config/config.go +++ b/config/config.go @@ -118,7 +118,7 @@ func (c *Config) Validate() error { blockchainIDToSubnetID[s.blockchainID] = s.subnetID } c.blockchainIDToSubnetID = blockchainIDToSubnetID - + return nil } diff --git a/relayer/manual_warp_message.go b/relayer/manual_warp_message.go deleted file mode 100644 index 7848e514..00000000 --- a/relayer/manual_warp_message.go +++ /dev/null @@ -1,2 +0,0 @@ -package relayer - diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index b7d34327..b221e090 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/awm-relayer/relayer" + avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" testUtils "github.com/ava-labs/awm-relayer/tests/utils" "github.com/ava-labs/subnet-evm/core/types" subnetEvmInterfaces "github.com/ava-labs/subnet-evm/interfaces" @@ -22,7 +23,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" . "github.com/onsi/gomega" ) From 6322ac813566b9f1e96940dcfd0b98becbc3b6e2 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 20 Jun 2024 13:34:50 -0400 Subject: [PATCH 24/56] wip --- main/main.go | 1 + relayer/application_relayer.go | 1 + relayer/relay_message_api_handler.go | 4 ++-- tests/e2e_test.go | 2 +- .../{teleporter_registry.go => manual_message.go} | 15 ++++++--------- tests/relay_message_api.go | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) rename tests/{teleporter_registry.go => manual_message.go} (92%) diff --git a/main/main.go b/main/main.go index 09c98658..7912bddf 100644 --- a/main/main.go +++ b/main/main.go @@ -216,6 +216,7 @@ func main() { // Initialize the API after the message coordinator is set http.Handle("/health", health.NewHandler(checker)) + http.HandleFunc(relayer.RelayApiPath, relayer.RelayAPIHandler) http.HandleFunc(relayer.RelayMessageApiPath, relayer.RelayMessageAPIHandler) // start the health check server diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go index 1755fd8d..52409b06 100644 --- a/relayer/application_relayer.go +++ b/relayer/application_relayer.go @@ -246,6 +246,7 @@ func (r *ApplicationRelayer) relayMessage( r.logger.Info( "Finished relaying message to destination chain", zap.String("destinationBlockchainID", r.relayerID.DestinationBlockchainID.String()), + zap.String("txHash", txHash.Hex()), ) r.incSuccessfulRelayMessageCount() diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go index 5acb6fda..6c266b30 100644 --- a/relayer/relay_message_api_handler.go +++ b/relayer/relay_message_api_handler.go @@ -35,7 +35,7 @@ type ManualWarpMessage struct { DestinationAddress common.Address } -func RelayAPIHandler(w http.ResponseWriter, r *http.Request) { +func RelayMessageAPIHandler(w http.ResponseWriter, r *http.Request) { var req ManualWarpMessage err := json.NewDecoder(r.Body).Decode(&req) @@ -64,7 +64,7 @@ func RelayAPIHandler(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) } -func RelayMessageAPIHandler(w http.ResponseWriter, r *http.Request) { +func RelayAPIHandler(w http.ResponseWriter, r *http.Request) { var req RelayMessageRequest err := json.NewDecoder(r.Body).Decode(&req) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index b9af3b8f..6dfe4c66 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -62,7 +62,7 @@ var _ = ginkgo.AfterSuite(func() { }) var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { - ginkgo.It("Manually Provided Message", func() { + ginkgo.FIt("Manually Provided Message", func() { ManualMessage(localNetworkInstance) }) ginkgo.It("Basic Relay", func() { diff --git a/tests/teleporter_registry.go b/tests/manual_message.go similarity index 92% rename from tests/teleporter_registry.go rename to tests/manual_message.go index de61079b..549790d9 100644 --- a/tests/teleporter_registry.go +++ b/tests/manual_message.go @@ -17,7 +17,6 @@ import ( "github.com/ava-labs/awm-relayer/relayer" 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" teleporterTestUtils "github.com/ava-labs/teleporter/tests/utils" "github.com/ethereum/go-ethereum/common" @@ -94,16 +93,14 @@ func ManualMessage(network interfaces.LocalNetwork) { ) relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname) - // Subscribe to the destination chain - newHeadsC := make(chan *types.Header, 10) - sub, err := cChainInfo.WSClient.SubscribeNewHead(ctx, newHeadsC) - Expect(err).Should(BeNil()) - defer sub.Unsubscribe() - 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) + reqBody := relayer.ManualWarpMessage{ UnsignedMessageBytes: unsignedMessage.Bytes(), SourceBlockchainID: cChainInfo.BlockchainID, @@ -132,8 +129,8 @@ func ManualMessage(network interfaces.LocalNetwork) { Expect(err).Should(BeNil()) Expect(res.Status).Should(Equal("200 OK")) - newVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{}) + newVersion, err := cChainInfo.TeleporterRegistry.LatesatVersion(&bind.CallOpts{}) Expect(err).Should(BeNil()) - Expect(newVersion.Cmp(expectedNewVersion)).Should(Equal(0)) + Expect(newVersion.Uint64()).Should(Equal(expectedNewVersion.Uint64())) } } diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index b221e090..79a20929 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -75,7 +75,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { Timeout: 30 * time.Second, } - requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, relayer.RelayMessageApiPath) + requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, relayer.RelayApiPath) // Send request to API { From 6ed13b44fcc73c08f548044cf6a3fd201933945f Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 20 Jun 2024 13:53:52 -0400 Subject: [PATCH 25/56] lint --- tests/manual_message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/manual_message.go b/tests/manual_message.go index 549790d9..a5d05bcf 100644 --- a/tests/manual_message.go +++ b/tests/manual_message.go @@ -129,7 +129,7 @@ func ManualMessage(network interfaces.LocalNetwork) { Expect(err).Should(BeNil()) Expect(res.Status).Should(Equal("200 OK")) - newVersion, err := cChainInfo.TeleporterRegistry.LatesatVersion(&bind.CallOpts{}) + newVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{}) Expect(err).Should(BeNil()) Expect(newVersion.Uint64()).Should(Equal(expectedNewVersion.Uint64())) } From 358894bdd1b471a011d664f0946c6aa7ed16c66d Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 25 Jun 2024 12:17:17 -0400 Subject: [PATCH 26/56] Pass MessageCoordinator as param --- main/main.go | 7 +- relayer/listener.go | 6 +- relayer/message_coordinator.go | 29 +------ relayer/relay_message_api_handler.go | 114 ++++++++++++++------------- 4 files changed, 71 insertions(+), 85 deletions(-) diff --git a/main/main.go b/main/main.go index 7912bddf..faa272b4 100644 --- a/main/main.go +++ b/main/main.go @@ -212,12 +212,12 @@ func main() { logger.Fatal("Failed to create application relayers", zap.Error(err)) panic(err) } - relayer.SetMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) + messageCoordinator := relayer.NewMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) // Initialize the API after the message coordinator is set http.Handle("/health", health.NewHandler(checker)) - http.HandleFunc(relayer.RelayApiPath, relayer.RelayAPIHandler) - http.HandleFunc(relayer.RelayMessageApiPath, relayer.RelayMessageAPIHandler) + http.Handle(relayer.RelayApiPath, relayer.RelayAPIHandler(messageCoordinator)) + http.Handle(relayer.RelayMessageApiPath, relayer.RelayMessageAPIHandler(messageCoordinator)) // start the health check server go func() { @@ -243,6 +243,7 @@ func main() { isHealthy, cfg.ProcessMissedBlocks, minHeights[sourceBlockchain.GetBlockchainID()], + messageCoordinator, ) }) } diff --git a/relayer/listener.go b/relayer/listener.go index da722248..0924f2f4 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -36,6 +36,7 @@ type Listener struct { catchUpResultChan chan bool healthStatus *atomic.Bool ethClient ethclient.Client + messageCoordinator *MessageCoordinator } // runListener creates a Listener instance and the ApplicationRelayers for a subnet. @@ -48,6 +49,7 @@ func RunListener( relayerHealth *atomic.Bool, processMissedBlocks bool, minHeight uint64, + messageCoordinator *MessageCoordinator, ) error { // Create the Listener listener, err := newListener( @@ -58,6 +60,7 @@ func RunListener( relayerHealth, processMissedBlocks, minHeight, + messageCoordinator, ) if err != nil { return fmt.Errorf("failed to create listener instance: %w", err) @@ -81,6 +84,7 @@ func newListener( relayerHealth *atomic.Bool, processMissedBlocks bool, startingHeight uint64, + messageCoordinator *MessageCoordinator, ) (*Listener, error) { blockchainID, err := ids.FromString(sourceBlockchain.BlockchainID) if err != nil { @@ -197,7 +201,7 @@ func (lstnr *Listener) processLogs(ctx context.Context) error { return fmt.Errorf("failed to catch up on historical blocks") } case blockHeader := <-lstnr.Subscriber.Headers(): - go ProcessBlock(blockHeader, lstnr.ethClient, errChan) + go lstnr.messageCoordinator.processBlock(blockHeader, lstnr.ethClient, errChan) case err := <-lstnr.Subscriber.Err(): lstnr.healthStatus.Store(false) lstnr.logger.Error( diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index d9d71eb6..c020fd99 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -19,8 +19,6 @@ import ( "go.uber.org/zap" ) -var globalMessageCoordinator *MessageCoordinator - type MessageCoordinator struct { logger logging.Logger // Maps Source blockchain ID and protocol address to a Message Handler Factory @@ -29,13 +27,13 @@ type MessageCoordinator struct { SourceClients map[ids.ID]ethclient.Client } -func SetMessageCoordinator( +func NewMessageCoordinator( logger logging.Logger, messageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory, applicationRelayers map[common.Hash]*ApplicationRelayer, sourceClients map[ids.ID]ethclient.Client, -) { - globalMessageCoordinator = &MessageCoordinator{ +) *MessageCoordinator { + return &MessageCoordinator{ logger: logger, MessageHandlerFactories: messageHandlerFactories, ApplicationRelayers: applicationRelayers, @@ -164,13 +162,6 @@ func (mc *MessageCoordinator) getApplicationRelayer( return nil } -func ProcessManualWarpMessage(warpMessage *relayerTypes.WarpMessageInfo) (common.Hash, error) { - if globalMessageCoordinator == nil { - return common.Hash{}, fmt.Errorf("global message coordinator not set") - } - return globalMessageCoordinator.processManualWarpMessage(warpMessage) -} - func (mc *MessageCoordinator) processManualWarpMessage( warpMessage *relayerTypes.WarpMessageInfo, ) (common.Hash, error) { @@ -193,13 +184,6 @@ func (mc *MessageCoordinator) processManualWarpMessage( return appRelayer.ProcessMessage(handler) } -func ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { - if globalMessageCoordinator == nil { - panic("global message coordinator not set") - } - return globalMessageCoordinator.processMessage(blockchainID, messageID, blockNum) -} - func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { ethClient, ok := mc.SourceClients[blockchainID] if !ok { @@ -230,13 +214,6 @@ func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID comm return appRelayer.ProcessMessage(handler) } -func ProcessBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { - if globalMessageCoordinator == nil { - panic("global message coordinator not set") - } - globalMessageCoordinator.processBlock(blockHeader, ethClient, errChan) -} - // Meant to be ran asynchronously. Errors should be sent to errChan. func (mc *MessageCoordinator) processBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { // Parse the logs in the block, and group by application relayer diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go index 6c266b30..1d7f155d 100644 --- a/relayer/relay_message_api_handler.go +++ b/relayer/relay_message_api_handler.go @@ -35,61 +35,65 @@ type ManualWarpMessage struct { DestinationAddress common.Address } -func RelayMessageAPIHandler(w http.ResponseWriter, r *http.Request) { - var req ManualWarpMessage - - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - warpMessageInfo := &relayerTypes.WarpMessageInfo{ - SourceAddress: req.SourceAddress, - UnsignedMessage: unsignedMessage, - } - - txHash, err := ProcessManualWarpMessage(warpMessageInfo) - if err != nil { - http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) - return - } - - _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) +func RelayMessageAPIHandler(messageCoordinator *MessageCoordinator) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req ManualWarpMessage + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + warpMessageInfo := &relayerTypes.WarpMessageInfo{ + SourceAddress: req.SourceAddress, + UnsignedMessage: unsignedMessage, + } + + txHash, err := messageCoordinator.processManualWarpMessage(warpMessageInfo) + if err != nil { + http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) + }) } -func RelayAPIHandler(w http.ResponseWriter, r *http.Request) { - var req RelayMessageRequest - - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - blockchainID, err := ids.FromString(req.BlockchainID) - if err != nil { - http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) - return - } - messageID := common.HexToHash(req.MessageID) - blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) - if !ok { - http.Error(w, "invalid blockNum", http.StatusBadRequest) - return - } - - txHash, err := ProcessMessage(blockchainID, messageID, blockNum) - if err != nil { - http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) - return - } - - _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) +func RelayAPIHandler(messageCoordinator *MessageCoordinator) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req RelayMessageRequest + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + blockchainID, err := ids.FromString(req.BlockchainID) + if err != nil { + http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) + return + } + messageID := common.HexToHash(req.MessageID) + blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) + if !ok { + http.Error(w, "invalid blockNum", http.StatusBadRequest) + return + } + + txHash, err := messageCoordinator.processMessage(blockchainID, messageID, blockNum) + if err != nil { + http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) + }) } From 3a67da1fced5014df3bb56adcae3760002bc900c Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 25 Jun 2024 12:17:57 -0400 Subject: [PATCH 27/56] Lint --- relayer/listener.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/relayer/listener.go b/relayer/listener.go index 0924f2f4..4ea3bd1f 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -28,14 +28,14 @@ const ( // Listener handles all messages sent from a given source chain type Listener struct { - Subscriber vms.Subscriber - currentRequestID uint32 - contractMessage vms.ContractMessage - logger logging.Logger - sourceBlockchain config.SourceBlockchain - catchUpResultChan chan bool - healthStatus *atomic.Bool - ethClient ethclient.Client + Subscriber vms.Subscriber + currentRequestID uint32 + contractMessage vms.ContractMessage + logger logging.Logger + sourceBlockchain config.SourceBlockchain + catchUpResultChan chan bool + healthStatus *atomic.Bool + ethClient ethclient.Client messageCoordinator *MessageCoordinator } From 53516b5584defa9c337bfb656ae62407b032ad4d Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 25 Jun 2024 13:02:16 -0400 Subject: [PATCH 28/56] Fix --- relayer/listener.go | 17 +++++++++-------- tests/e2e_test.go | 2 +- tests/manual_message.go | 3 +++ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/relayer/listener.go b/relayer/listener.go index 4ea3bd1f..d10fee59 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -126,14 +126,15 @@ func newListener( zap.String("blockchainIDHex", sourceBlockchain.GetBlockchainID().Hex()), ) lstnr := Listener{ - Subscriber: sub, - currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision - contractMessage: vms.NewContractMessage(logger, sourceBlockchain), - logger: logger, - sourceBlockchain: sourceBlockchain, - catchUpResultChan: catchUpResultChan, - healthStatus: relayerHealth, - ethClient: ethRPCClient, + Subscriber: sub, + currentRequestID: rand.Uint32(), // Initialize to a random value to mitigate requestID collision + contractMessage: vms.NewContractMessage(logger, sourceBlockchain), + logger: logger, + sourceBlockchain: sourceBlockchain, + catchUpResultChan: catchUpResultChan, + healthStatus: relayerHealth, + ethClient: ethRPCClient, + messageCoordinator: messageCoordinator, } // Open the subscription. We must do this before processing any missed messages, otherwise we may miss an incoming message diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 6dfe4c66..b9af3b8f 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -62,7 +62,7 @@ var _ = ginkgo.AfterSuite(func() { }) var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { - ginkgo.FIt("Manually Provided Message", func() { + ginkgo.It("Manually Provided Message", func() { ManualMessage(localNetworkInstance) }) ginkgo.It("Basic Relay", func() { diff --git a/tests/manual_message.go b/tests/manual_message.go index a5d05bcf..56759606 100644 --- a/tests/manual_message.go +++ b/tests/manual_message.go @@ -129,6 +129,9 @@ func ManualMessage(network interfaces.LocalNetwork) { Expect(err).Should(BeNil()) Expect(res.Status).Should(Equal("200 OK")) + // Wait for all nodes to see new transaction + time.Sleep(1 * time.Second) + newVersion, err := cChainInfo.TeleporterRegistry.LatestVersion(&bind.CallOpts{}) Expect(err).Should(BeNil()) Expect(newVersion.Uint64()).Should(Equal(expectedNewVersion.Uint64())) From 93e9a751911621d470c91f72847358873cef8dbc Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 25 Jun 2024 15:05:12 -0400 Subject: [PATCH 29/56] Refactor API --- main/main.go | 49 +++++--------- relayer/listener.go | 2 +- relayer/message_coordinator.go | 6 +- relayer/relay_message_api_handler.go | 99 ---------------------------- tests/manual_message.go | 6 +- tests/relay_message_api.go | 7 +- 6 files changed, 26 insertions(+), 143 deletions(-) delete mode 100644 relayer/relay_message_api_handler.go diff --git a/main/main.go b/main/main.go index faa272b4..870c277f 100644 --- a/main/main.go +++ b/main/main.go @@ -15,12 +15,12 @@ import ( offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" "github.com/ava-labs/awm-relayer/messages/teleporter" - "github.com/alexliesenfeld/health" "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" "github.com/ava-labs/avalanchego/utils/constants" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/awm-relayer/api" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/database" "github.com/ava-labs/awm-relayer/peers" @@ -124,7 +124,6 @@ func main() { // Initialize the global app request network logger.Info("Initializing app request network") - // The app request network generates P2P networking logs that are verbose at the info level. // Unless the log level is debug or lower, set the network log level to error to avoid spamming the logs. networkLogLevel := logging.Error @@ -141,29 +140,6 @@ func main() { panic(err) } - // Each goroutine will have an atomic bool that it can set to false if it ever disconnects from its subscription. - relayerHealth := make(map[ids.ID]*atomic.Bool) - - checker := health.NewChecker( - health.WithCheck(health.Check{ - Name: "relayers-all", - Check: func(context.Context) error { - // Store the IDs as the cb58 encoding - var unhealthyRelayers []string - for id, health := range relayerHealth { - if !health.Load() { - unhealthyRelayers = append(unhealthyRelayers, id.String()) - } - } - - if len(unhealthyRelayers) > 0 { - return fmt.Errorf("relayers are unhealthy for blockchains %v", unhealthyRelayers) - } - return nil - }, - }), - ) - startMetricsServer(logger, gatherer, cfg.MetricsPort) relayerMetrics, err := relayer.NewApplicationRelayerMetrics(registerer) @@ -190,6 +166,8 @@ func main() { ticker := utils.NewTicker(cfg.DBWriteIntervalSeconds) go ticker.Run() + relayerHealth := createHealthTrackers(&cfg) + messageHandlerFactories, err := createMessageHandlerFactories(logger, &cfg) if err != nil { logger.Fatal("Failed to create message handler factories", zap.Error(err)) @@ -214,10 +192,10 @@ func main() { } messageCoordinator := relayer.NewMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) - // Initialize the API after the message coordinator is set - http.Handle("/health", health.NewHandler(checker)) - http.Handle(relayer.RelayApiPath, relayer.RelayAPIHandler(messageCoordinator)) - http.Handle(relayer.RelayMessageApiPath, relayer.RelayMessageAPIHandler(messageCoordinator)) + // Each goroutine will have an atomic bool that it can set to false if it ever disconnects from its subscription. + api.HandleHealthCheck(relayerHealth) + api.HandleRelay(messageCoordinator) + api.HandleRelayMessage(messageCoordinator) // start the health check server go func() { @@ -229,9 +207,6 @@ func main() { for _, s := range cfg.SourceBlockchains { sourceBlockchain := s - isHealthy := atomic.NewBool(true) - relayerHealth[s.GetBlockchainID()] = isHealthy - // errgroup will cancel the context when the first goroutine returns an error errGroup.Go(func() error { // runListener runs until it errors or the context is cancelled by another goroutine @@ -240,7 +215,7 @@ func main() { logger, *sourceBlockchain, sourceClients[sourceBlockchain.GetBlockchainID()], - isHealthy, + relayerHealth[sourceBlockchain.GetBlockchainID()], cfg.ProcessMissedBlocks, minHeights[sourceBlockchain.GetBlockchainID()], messageCoordinator, @@ -446,6 +421,14 @@ func createApplicationRelayersForSourceChain( return applicationRelayers, minHeight, nil } +func createHealthTrackers(cfg *config.Config) map[ids.ID]*atomic.Bool { + healthTrackers := make(map[ids.ID]*atomic.Bool, len(cfg.SourceBlockchains)) + for _, sourceBlockchain := range cfg.SourceBlockchains { + healthTrackers[sourceBlockchain.GetBlockchainID()] = atomic.NewBool(true) + } + return healthTrackers +} + func startMetricsServer(logger logging.Logger, gatherer prometheus.Gatherer, port uint16) { http.Handle("/metrics", promhttp.HandlerFor(gatherer, promhttp.HandlerOpts{})) diff --git a/relayer/listener.go b/relayer/listener.go index d10fee59..b3eb73ed 100644 --- a/relayer/listener.go +++ b/relayer/listener.go @@ -202,7 +202,7 @@ func (lstnr *Listener) processLogs(ctx context.Context) error { return fmt.Errorf("failed to catch up on historical blocks") } case blockHeader := <-lstnr.Subscriber.Headers(): - go lstnr.messageCoordinator.processBlock(blockHeader, lstnr.ethClient, errChan) + go lstnr.messageCoordinator.ProcessBlock(blockHeader, lstnr.ethClient, errChan) case err := <-lstnr.Subscriber.Err(): lstnr.healthStatus.Store(false) lstnr.logger.Error( diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index c020fd99..db90795f 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -162,7 +162,7 @@ func (mc *MessageCoordinator) getApplicationRelayer( return nil } -func (mc *MessageCoordinator) processManualWarpMessage( +func (mc *MessageCoordinator) ProcessManualWarpMessage( warpMessage *relayerTypes.WarpMessageInfo, ) (common.Hash, error) { // Send any messages that were specified in the configuration @@ -184,7 +184,7 @@ func (mc *MessageCoordinator) processManualWarpMessage( return appRelayer.ProcessMessage(handler) } -func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { +func (mc *MessageCoordinator) ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { ethClient, ok := mc.SourceClients[blockchainID] if !ok { mc.logger.Error("Source client not found", zap.String("blockchainID", blockchainID.String())) @@ -215,7 +215,7 @@ func (mc *MessageCoordinator) processMessage(blockchainID ids.ID, messageID comm } // Meant to be ran asynchronously. Errors should be sent to errChan. -func (mc *MessageCoordinator) processBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { +func (mc *MessageCoordinator) ProcessBlock(blockHeader *types.Header, ethClient ethclient.Client, errChan chan error) { // Parse the logs in the block, and group by application relayer block, err := relayerTypes.NewWarpBlockInfo(blockHeader, ethClient) if err != nil { diff --git a/relayer/relay_message_api_handler.go b/relayer/relay_message_api_handler.go deleted file mode 100644 index 1d7f155d..00000000 --- a/relayer/relay_message_api_handler.go +++ /dev/null @@ -1,99 +0,0 @@ -package relayer - -import ( - "encoding/json" - "math/big" - "net/http" - - "github.com/ava-labs/awm-relayer/types" - relayerTypes "github.com/ava-labs/awm-relayer/types" - - "github.com/ava-labs/avalanchego/ids" - "github.com/ethereum/go-ethereum/common" -) - -const ( - RelayApiPath = "/relay" - RelayMessageApiPath = RelayApiPath + "/message" -) - -type RelayMessageRequest struct { - // Required. cb58 encoding of the blockchain ID - BlockchainID string `json:"blockchain-id"` - // Required. Hex encoding of the warp message ID - MessageID string `json:"message-id"` - // Required. Integer representation of the block number - BlockNum string `json:"block-num"` -} - -// Defines a manual warp message to be sent from the relayer through the API. -type ManualWarpMessage struct { - UnsignedMessageBytes []byte - SourceBlockchainID ids.ID - DestinationBlockchainID ids.ID - SourceAddress common.Address - DestinationAddress common.Address -} - -func RelayMessageAPIHandler(messageCoordinator *MessageCoordinator) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req ManualWarpMessage - - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - warpMessageInfo := &relayerTypes.WarpMessageInfo{ - SourceAddress: req.SourceAddress, - UnsignedMessage: unsignedMessage, - } - - txHash, err := messageCoordinator.processManualWarpMessage(warpMessageInfo) - if err != nil { - http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) - return - } - - _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) - }) -} - -func RelayAPIHandler(messageCoordinator *MessageCoordinator) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req RelayMessageRequest - - err := json.NewDecoder(r.Body).Decode(&req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - blockchainID, err := ids.FromString(req.BlockchainID) - if err != nil { - http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) - return - } - messageID := common.HexToHash(req.MessageID) - blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) - if !ok { - http.Error(w, "invalid blockNum", http.StatusBadRequest) - return - } - - txHash, err := messageCoordinator.processMessage(blockchainID, messageID, blockNum) - if err != nil { - http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) - return - } - - _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) - }) -} diff --git a/tests/manual_message.go b/tests/manual_message.go index 56759606..92fd2e40 100644 --- a/tests/manual_message.go +++ b/tests/manual_message.go @@ -13,8 +13,8 @@ import ( "time" runner_sdk "github.com/ava-labs/avalanche-network-runner/client" + "github.com/ava-labs/awm-relayer/api" offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" - "github.com/ava-labs/awm-relayer/relayer" testUtils "github.com/ava-labs/awm-relayer/tests/utils" "github.com/ava-labs/subnet-evm/accounts/abi/bind" "github.com/ava-labs/teleporter/tests/interfaces" @@ -101,7 +101,7 @@ func ManualMessage(network interfaces.LocalNetwork) { log.Info("Waiting for the relayer to start up") time.Sleep(15 * time.Second) - reqBody := relayer.ManualWarpMessage{ + reqBody := api.ManualWarpMessage{ UnsignedMessageBytes: unsignedMessage.Bytes(), SourceBlockchainID: cChainInfo.BlockchainID, DestinationBlockchainID: cChainInfo.BlockchainID, @@ -113,7 +113,7 @@ func ManualMessage(network interfaces.LocalNetwork) { Timeout: 30 * time.Second, } - requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, relayer.RelayMessageApiPath) + requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayMessageApiPath) // Send request to API { diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index 79a20929..6483531e 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -11,9 +11,8 @@ import ( "net/http" "time" - "github.com/ava-labs/awm-relayer/relayer" - avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" + "github.com/ava-labs/awm-relayer/api" testUtils "github.com/ava-labs/awm-relayer/tests/utils" "github.com/ava-labs/subnet-evm/core/types" subnetEvmInterfaces "github.com/ava-labs/subnet-evm/interfaces" @@ -65,7 +64,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { log.Info("Waiting for the relayer to start up") time.Sleep(15 * time.Second) - reqBody := relayer.RelayMessageRequest{ + reqBody := api.RelayMessageRequest{ BlockchainID: subnetAInfo.BlockchainID.String(), MessageID: warpMessage.ID().Hex(), BlockNum: receipt.BlockNumber.String(), @@ -75,7 +74,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { Timeout: 30 * time.Second, } - requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, relayer.RelayApiPath) + requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayApiPath) // Send request to API { From daac2cdfe3203058a58b9ee00e124c41367d0ef8 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 25 Jun 2024 15:07:16 -0400 Subject: [PATCH 30/56] Add api folder --- api/health_check.go | 39 ++++++++++++++++ api/relay_message.go | 108 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 api/health_check.go create mode 100644 api/relay_message.go diff --git a/api/health_check.go b/api/health_check.go new file mode 100644 index 00000000..10feaf56 --- /dev/null +++ b/api/health_check.go @@ -0,0 +1,39 @@ +package api + +import ( + "context" + "fmt" + "net/http" + + "github.com/alexliesenfeld/health" + "github.com/ava-labs/avalanchego/ids" + "go.uber.org/atomic" +) + +const HealthAPIPath = "/health" + +func HandleHealthCheck(relayerHealth map[ids.ID]*atomic.Bool) { + http.Handle(HealthAPIPath, healthCheckHandler(relayerHealth)) +} + +func healthCheckHandler(relayerHealth map[ids.ID]*atomic.Bool) http.Handler { + return health.NewHandler(health.NewChecker( + health.WithCheck(health.Check{ + Name: "relayers-all", + Check: func(context.Context) error { + // Store the IDs as the cb58 encoding + var unhealthyRelayers []string + for id, health := range relayerHealth { + if !health.Load() { + unhealthyRelayers = append(unhealthyRelayers, id.String()) + } + } + + if len(unhealthyRelayers) > 0 { + return fmt.Errorf("relayers are unhealthy for blockchains %v", unhealthyRelayers) + } + return nil + }, + }), + )) +} diff --git a/api/relay_message.go b/api/relay_message.go new file mode 100644 index 00000000..4aa0570c --- /dev/null +++ b/api/relay_message.go @@ -0,0 +1,108 @@ +package api + +import ( + "encoding/json" + "math/big" + "net/http" + + "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/avalanchego/ids" + "github.com/ethereum/go-ethereum/common" +) + +const ( + RelayApiPath = "/relay" + RelayMessageApiPath = RelayApiPath + "/message" +) + +type RelayMessageRequest struct { + // Required. cb58 encoding of the blockchain ID + BlockchainID string `json:"blockchain-id"` + // Required. Hex encoding of the warp message ID + MessageID string `json:"message-id"` + // Required. Integer representation of the block number + BlockNum string `json:"block-num"` +} + +// Defines a manual warp message to be sent from the relayer through the API. +type ManualWarpMessage struct { + UnsignedMessageBytes []byte + SourceBlockchainID ids.ID + DestinationBlockchainID ids.ID + SourceAddress common.Address + DestinationAddress common.Address +} + +func HandleRelayMessage(messageCoordinator *relayer.MessageCoordinator) { + http.Handle(RelayApiPath, relayAPIHandler(messageCoordinator)) +} + +func HandleRelay(messageCoordinator *relayer.MessageCoordinator) { + http.Handle(RelayMessageApiPath, relayMessageAPIHandler(messageCoordinator)) +} + +func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req ManualWarpMessage + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + warpMessageInfo := &relayerTypes.WarpMessageInfo{ + SourceAddress: req.SourceAddress, + UnsignedMessage: unsignedMessage, + } + + txHash, err := messageCoordinator.ProcessManualWarpMessage(warpMessageInfo) + if err != nil { + http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) + }) +} + +func relayAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var req RelayMessageRequest + + err := json.NewDecoder(r.Body).Decode(&req) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + blockchainID, err := ids.FromString(req.BlockchainID) + if err != nil { + http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) + return + } + messageID := common.HexToHash(req.MessageID) + blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) + if !ok { + http.Error(w, "invalid blockNum", http.StatusBadRequest) + return + } + + txHash, err := messageCoordinator.ProcessMessage(blockchainID, messageID, blockNum) + if err != nil { + http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) + }) +} From 45657f7ea105a66ae79b67b894f354e7ac8b744e Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 25 Jun 2024 16:48:51 -0400 Subject: [PATCH 31/56] Make message coordinator fields private --- relayer/message_coordinator.go | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index db90795f..73f76000 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -22,9 +22,9 @@ import ( type MessageCoordinator struct { logger logging.Logger // Maps Source blockchain ID and protocol address to a Message Handler Factory - MessageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory - ApplicationRelayers map[common.Hash]*ApplicationRelayer - SourceClients map[ids.ID]ethclient.Client + messageHandlerFactories map[ids.ID]map[common.Address]messages.MessageHandlerFactory + applicationRelayers map[common.Hash]*ApplicationRelayer + sourceClients map[ids.ID]ethclient.Client } func NewMessageCoordinator( @@ -35,9 +35,9 @@ func NewMessageCoordinator( ) *MessageCoordinator { return &MessageCoordinator{ logger: logger, - MessageHandlerFactories: messageHandlerFactories, - ApplicationRelayers: applicationRelayers, - SourceClients: sourceClients, + messageHandlerFactories: messageHandlerFactories, + applicationRelayers: applicationRelayers, + sourceClients: sourceClients, } } @@ -53,7 +53,7 @@ func (mc *MessageCoordinator) GetAppRelayerMessageHandler( error, ) { // Check that the warp message is from a supported message protocol contract address. - messageHandlerFactory, supportedMessageProtocol := mc.MessageHandlerFactories[warpMessageInfo.UnsignedMessage.SourceChainID][warpMessageInfo.SourceAddress] + messageHandlerFactory, supportedMessageProtocol := mc.messageHandlerFactories[warpMessageInfo.UnsignedMessage.SourceChainID][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. @@ -116,7 +116,7 @@ func (mc *MessageCoordinator) getApplicationRelayer( originSenderAddress, destinationAddress, ) - if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok { return applicationRelayer } @@ -127,7 +127,7 @@ func (mc *MessageCoordinator) getApplicationRelayer( originSenderAddress, database.AllAllowedAddress, ) - if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok { return applicationRelayer } @@ -138,7 +138,7 @@ func (mc *MessageCoordinator) getApplicationRelayer( database.AllAllowedAddress, destinationAddress, ) - if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok { return applicationRelayer } @@ -149,7 +149,7 @@ func (mc *MessageCoordinator) getApplicationRelayer( database.AllAllowedAddress, database.AllAllowedAddress, ) - if applicationRelayer, ok := mc.ApplicationRelayers[applicationRelayerID]; ok { + if applicationRelayer, ok := mc.applicationRelayers[applicationRelayerID]; ok { return applicationRelayer } mc.logger.Debug( @@ -185,7 +185,7 @@ func (mc *MessageCoordinator) ProcessManualWarpMessage( } func (mc *MessageCoordinator) ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { - ethClient, ok := mc.SourceClients[blockchainID] + ethClient, ok := mc.sourceClients[blockchainID] if !ok { mc.logger.Error("Source client not found", zap.String("blockchainID", blockchainID.String())) return common.Hash{}, fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) @@ -244,7 +244,7 @@ func (mc *MessageCoordinator) ProcessBlock(blockHeader *types.Header, ethClient messageHandlers[appRelayer.relayerID.ID] = append(messageHandlers[appRelayer.relayerID.ID], handler) } // Initiate message relay of all registered messages - for _, appRelayer := range mc.ApplicationRelayers { + for _, appRelayer := range mc.applicationRelayers { // 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] From a77d2449502fa586e857b741bf5fb8b5d9329d6e Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 26 Jun 2024 12:29:42 -0400 Subject: [PATCH 32/56] lint --- relayer/application_relayer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go index abb3d3a9..9c6c36d4 100644 --- a/relayer/application_relayer.go +++ b/relayer/application_relayer.go @@ -30,8 +30,8 @@ import ( "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" - "github.com/ethereum/go-ethereum/common" "github.com/ava-labs/subnet-evm/rpc" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "golang.org/x/sync/errgroup" From 14810a27805598dffd99386bf8df06b3af3b1395 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 26 Jun 2024 13:10:03 -0400 Subject: [PATCH 33/56] Remove unneeded fields --- api/relay_message.go | 11 ++++------- tests/manual_message.go | 9 +++------ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/api/relay_message.go b/api/relay_message.go index 4aa0570c..73448355 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -28,12 +28,9 @@ type RelayMessageRequest struct { } // Defines a manual warp message to be sent from the relayer through the API. -type ManualWarpMessage struct { - UnsignedMessageBytes []byte - SourceBlockchainID ids.ID - DestinationBlockchainID ids.ID - SourceAddress common.Address - DestinationAddress common.Address +type ManualWarpMessageRequest struct { + UnsignedMessageBytes []byte + SourceAddress common.Address } func HandleRelayMessage(messageCoordinator *relayer.MessageCoordinator) { @@ -46,7 +43,7 @@ func HandleRelay(messageCoordinator *relayer.MessageCoordinator) { func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req ManualWarpMessage + var req ManualWarpMessageRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { diff --git a/tests/manual_message.go b/tests/manual_message.go index 92fd2e40..16eff7ad 100644 --- a/tests/manual_message.go +++ b/tests/manual_message.go @@ -101,12 +101,9 @@ func ManualMessage(network interfaces.LocalNetwork) { log.Info("Waiting for the relayer to start up") time.Sleep(15 * time.Second) - reqBody := api.ManualWarpMessage{ - UnsignedMessageBytes: unsignedMessage.Bytes(), - SourceBlockchainID: cChainInfo.BlockchainID, - DestinationBlockchainID: cChainInfo.BlockchainID, - SourceAddress: offchainregistry.OffChainRegistrySourceAddress, - DestinationAddress: cChainInfo.TeleporterRegistryAddress, + reqBody := api.ManualWarpMessageRequest{ + UnsignedMessageBytes: unsignedMessage.Bytes(), + SourceAddress: offchainregistry.OffChainRegistrySourceAddress, } client := http.Client{ From 59ec4d4c3abcbd8a84d1e8eeff881f512ca4227a Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 26 Jun 2024 15:58:10 -0400 Subject: [PATCH 34/56] Update main/main.go Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Signed-off-by: Geoff Stuart --- main/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.go b/main/main.go index 0f5e116d..5b813ceb 100644 --- a/main/main.go +++ b/main/main.go @@ -200,7 +200,7 @@ func main() { } messageCoordinator := relayer.NewMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) - // Each goroutine will have an atomic bool that it can set to false if it ever disconnects from its subscription. + // Each Listener goroutine will have an atomic bool that it can set to false to indicate an unrecoverable error api.HandleHealthCheck(relayerHealth) api.HandleRelay(messageCoordinator) api.HandleRelayMessage(messageCoordinator) From 214abb8e833db8f615382b2f5f8a47adca80f7b8 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Wed, 26 Jun 2024 15:58:16 -0400 Subject: [PATCH 35/56] Update main/main.go Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Signed-off-by: Geoff Stuart --- main/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/main.go b/main/main.go index 5b813ceb..6d3105c4 100644 --- a/main/main.go +++ b/main/main.go @@ -241,7 +241,7 @@ func createMessageHandlerFactories( messageHandlerFactories := make(map[ids.ID]map[common.Address]messages.MessageHandlerFactory) for _, sourceBlockchain := range globalConfig.SourceBlockchains { messageHandlerFactoriesForSource := make(map[common.Address]messages.MessageHandlerFactory) - // Create message managers for each supported message protocol + // Create message handler factories for each supported message protocol for addressStr, cfg := range sourceBlockchain.MessageContracts { address := common.HexToAddress(addressStr) format := cfg.MessageFormat From a39bdcff48239ec566eb95f29f6987ea7604b43f Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 10:17:40 -0400 Subject: [PATCH 36/56] Update relayer/message_coordinator.go Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com> Signed-off-by: Geoff Stuart --- relayer/message_coordinator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 73f76000..197a250a 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -41,7 +41,7 @@ func NewMessageCoordinator( } } -// GetAppRelayerMessageHandler Returns the ApplicationRelayer that is configured to handle this message, as well as a +// GetAppRelayerMessageHandler 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. From 74fd9025379810cc5822f4c5c93ff64c14cf80b7 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 10:17:56 -0400 Subject: [PATCH 37/56] Update api/relay_message.go Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com> Signed-off-by: Geoff Stuart --- api/relay_message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/relay_message.go b/api/relay_message.go index 73448355..177226d3 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -19,7 +19,7 @@ const ( ) type RelayMessageRequest struct { - // Required. cb58 encoding of the blockchain ID + // Required. cb58 encoding of the source blockchain ID for the message BlockchainID string `json:"blockchain-id"` // Required. Hex encoding of the warp message ID MessageID string `json:"message-id"` From cdd563539a782a8fabffb66cd1814e2fc01ef8a2 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 10:18:11 -0400 Subject: [PATCH 38/56] Update api/relay_message.go Co-authored-by: Michael Kaplan <55204436+michaelkaplan13@users.noreply.github.com> Signed-off-by: Geoff Stuart --- api/relay_message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/relay_message.go b/api/relay_message.go index 177226d3..b8c2217d 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -23,7 +23,7 @@ type RelayMessageRequest struct { BlockchainID string `json:"blockchain-id"` // Required. Hex encoding of the warp message ID MessageID string `json:"message-id"` - // Required. Integer representation of the block number + // Required. Integer representation of the block number that the message was sent in BlockNum string `json:"block-num"` } From f6e8e9cef1c3313b7dd9fb46c7bd78953930e85d Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 10:25:14 -0400 Subject: [PATCH 39/56] Review fixes --- api/relay_message.go | 8 ++++---- tests/basic_relay.go | 8 ++++++-- tests/manual_message.go | 2 +- tests/relay_message_api.go | 2 +- tests/utils/utils.go | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/api/relay_message.go b/api/relay_message.go index b8c2217d..3cc0ba7f 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -14,8 +14,8 @@ import ( ) const ( - RelayApiPath = "/relay" - RelayMessageApiPath = RelayApiPath + "/message" + RelayAPIPath = "/relay" + RelayMessageAPIPath = RelayAPIPath + "/message" ) type RelayMessageRequest struct { @@ -34,11 +34,11 @@ type ManualWarpMessageRequest struct { } func HandleRelayMessage(messageCoordinator *relayer.MessageCoordinator) { - http.Handle(RelayApiPath, relayAPIHandler(messageCoordinator)) + http.Handle(RelayAPIPath, relayAPIHandler(messageCoordinator)) } func HandleRelay(messageCoordinator *relayer.MessageCoordinator) { - http.Handle(RelayMessageApiPath, relayMessageAPIHandler(messageCoordinator)) + http.Handle(RelayMessageAPIPath, relayMessageAPIHandler(messageCoordinator)) } func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { diff --git a/tests/basic_relay.go b/tests/basic_relay.go index 2be9c9bb..28bd9735 100644 --- a/tests/basic_relay.go +++ b/tests/basic_relay.go @@ -52,6 +52,8 @@ func BasicRelay(network interfaces.LocalNetwork) { fundedAddress, relayerKey, ) + // The config needs to be validated in order to be passed to database.GetConfigRelayerIDs + relayerConfig.Validate() relayerConfigPath := testUtils.WriteRelayerConfig(relayerConfig, testUtils.DefaultRelayerCfgFname) @@ -114,8 +116,10 @@ func BasicRelay(network interfaces.LocalNetwork) { relayerIDA := database.CalculateRelayerID(subnetAInfo.BlockchainID, subnetBInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress) relayerIDB := database.CalculateRelayerID(subnetBInfo.BlockchainID, subnetAInfo.BlockchainID, database.AllAllowedAddress, database.AllAllowedAddress) // Modify the JSON database to force the relayer to re-process old blocks - _ = jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0")) - _ = jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0")) + err = jsonDB.Put(relayerIDA, database.LatestProcessedBlockKey, []byte("0")) + Expect(err).Should(BeNil()) + err = jsonDB.Put(relayerIDB, database.LatestProcessedBlockKey, []byte("0")) + Expect(err).Should(BeNil()) // Subscribe to the destination chain newHeadsB := make(chan *types.Header, 10) diff --git a/tests/manual_message.go b/tests/manual_message.go index 16eff7ad..53452dfc 100644 --- a/tests/manual_message.go +++ b/tests/manual_message.go @@ -110,7 +110,7 @@ func ManualMessage(network interfaces.LocalNetwork) { Timeout: 30 * time.Second, } - requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayMessageApiPath) + requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayMessageAPIPath) // Send request to API { diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index 6483531e..d71342a9 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -74,7 +74,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { Timeout: 30 * time.Second, } - requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayApiPath) + requestURL := fmt.Sprintf("http://localhost:%d%s", relayerConfig.APIPort, api.RelayAPIPath) // Send request to API { diff --git a/tests/utils/utils.go b/tests/utils/utils.go index fe8493ed..170b16b8 100644 --- a/tests/utils/utils.go +++ b/tests/utils/utils.go @@ -204,7 +204,7 @@ func CreateDefaultRelayerConfig( MetricsPort: 9090, SourceBlockchains: sources, DestinationBlockchains: destinations, - APIPort: 1234, + APIPort: 8080, } } From 753bf5be6efdf81faf4ba3f248b084902362d2d3 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 12:43:27 -0400 Subject: [PATCH 40/56] Review fixes --- api/relay_message.go | 31 +++++++++++++++++++++++++--- messages/message_handler.go | 1 + relayer/application_relayer.go | 7 +++---- relayer/message_coordinator.go | 37 +++++++++++++++++++++++++++------- tests/e2e_test.go | 6 ++---- tests/relay_message_api.go | 28 ++++++++++++++++++++++++- types/types.go | 25 ++++------------------- 7 files changed, 95 insertions(+), 40 deletions(-) diff --git a/api/relay_message.go b/api/relay_message.go index 3cc0ba7f..bb1c3271 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -27,6 +27,11 @@ type RelayMessageRequest struct { BlockNum string `json:"block-num"` } +type RelayMessageResponse struct { + // hex encoding of the source blockchain ID for the message + TransactionHash string `json:"transaction-hash"` +} + // Defines a manual warp message to be sent from the relayer through the API. type ManualWarpMessageRequest struct { UnsignedMessageBytes []byte @@ -68,7 +73,17 @@ func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http return } - _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) + resp, err := json.Marshal( + RelayMessageResponse{ + TransactionHash: txHash.Hex(), + }, + ) + if err != nil { + http.Error(w, "error writing response: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write(resp) }) } @@ -94,12 +109,22 @@ func relayAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handle return } - txHash, err := messageCoordinator.ProcessMessage(blockchainID, messageID, blockNum) + txHash, err := messageCoordinator.ProcessMessageID(blockchainID, messageID, blockNum) if err != nil { http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) return } - _, _ = w.Write([]byte("Message processed successfully. Transaction Hash: " + txHash.Hex())) + resp, err := json.Marshal( + RelayMessageResponse{ + TransactionHash: txHash.Hex(), + }, + ) + if err != nil { + http.Error(w, "error writing response: "+err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write(resp) }) } diff --git a/messages/message_handler.go b/messages/message_handler.go index 428a8fd1..3beac8bc 100644 --- a/messages/message_handler.go +++ b/messages/message_handler.go @@ -27,6 +27,7 @@ type MessageHandler interface { // 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 + // returns the transaction hash if the transaction is successful. SendMessage(signedMessage *warp.Message, destinationClient vms.DestinationClient) (common.Hash, error) // GetMessageRoutingInfo returns the source chain ID, origin sender address, destination chain ID, and destination address diff --git a/relayer/application_relayer.go b/relayer/application_relayer.go index 9c6c36d4..d2ba8c08 100644 --- a/relayer/application_relayer.go +++ b/relayer/application_relayer.go @@ -184,6 +184,7 @@ func (r *ApplicationRelayer) ProcessHeight(height uint64, handlers []messages.Me } // Relays a message to the destination chain. Does not checkpoint the height. +// returns the transaction hash if the message is successfully relayed. func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) (common.Hash, error) { // Increment the request ID. Make sure we don't hold the lock while we relay the message. r.lock.Lock() @@ -191,16 +192,14 @@ func (r *ApplicationRelayer) ProcessMessage(handler messages.MessageHandler) (co reqID := r.currentRequestID r.lock.Unlock() - return r.relayMessage( - reqID, - handler, - ) + return r.relayMessage(reqID, handler) } func (r *ApplicationRelayer) RelayerID() database.RelayerID { return r.relayerID } +// returns the transaction hash if the message is successfully relayed. func (r *ApplicationRelayer) relayMessage( requestID uint32, handler messages.MessageHandler, diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 197a250a..6c3f8455 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -4,6 +4,7 @@ package relayer import ( + "context" "errors" "fmt" "math/big" @@ -15,10 +16,15 @@ import ( relayerTypes "github.com/ava-labs/awm-relayer/types" "github.com/ava-labs/subnet-evm/core/types" "github.com/ava-labs/subnet-evm/ethclient" + "github.com/ava-labs/subnet-evm/interfaces" + "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ethereum/go-ethereum/common" "go.uber.org/zap" ) +// MessageCoordinator contains all the logic required to process messages in the relayer. +// Other components such as the listeners or the API should pass messages to the MessageCoordinator +// so that it can parse the message(s) and pass them the the proper ApplicationRelayer. type MessageCoordinator struct { logger logging.Logger // Maps Source blockchain ID and protocol address to a Message Handler Factory @@ -41,11 +47,11 @@ func NewMessageCoordinator( } } -// GetAppRelayerMessageHandler returns the ApplicationRelayer that is configured to handle this message, as well as a +// getAppRelayerMessageHandler 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 (mc *MessageCoordinator) GetAppRelayerMessageHandler( +func (mc *MessageCoordinator) getAppRelayerMessageHandler( warpMessageInfo *relayerTypes.WarpMessageInfo, ) ( *ApplicationRelayer, @@ -171,7 +177,7 @@ func (mc *MessageCoordinator) ProcessManualWarpMessage( zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), ) - appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) + appRelayer, handler, err := mc.getAppRelayerMessageHandler(warpMessage) if err != nil { mc.logger.Error( "Failed to parse manual Warp message.", @@ -184,20 +190,20 @@ func (mc *MessageCoordinator) ProcessManualWarpMessage( return appRelayer.ProcessMessage(handler) } -func (mc *MessageCoordinator) ProcessMessage(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { +func (mc *MessageCoordinator) ProcessMessageID(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { ethClient, ok := mc.sourceClients[blockchainID] if !ok { mc.logger.Error("Source client not found", zap.String("blockchainID", blockchainID.String())) return common.Hash{}, fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) } - warpMessage, err := relayerTypes.FetchWarpMessageFromID(ethClient, messageID, blockNum) + warpMessage, err := FetchWarpMessageFromID(ethClient, messageID, blockNum) if err != nil { mc.logger.Error("Failed to fetch warp from blockchain", zap.String("blockchainID", blockchainID.String()), zap.Error(err)) return common.Hash{}, fmt.Errorf("could not fetch warp message from ID: %w", err) } - appRelayer, handler, err := mc.GetAppRelayerMessageHandler(warpMessage) + appRelayer, handler, err := mc.getAppRelayerMessageHandler(warpMessage) if err != nil { mc.logger.Error( "Failed to parse message", @@ -227,7 +233,7 @@ func (mc *MessageCoordinator) ProcessBlock(blockHeader *types.Header, ethClient // 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 := mc.GetAppRelayerMessageHandler(warpLogInfo) + appRelayer, handler, err := mc.getAppRelayerMessageHandler(warpLogInfo) if err != nil { mc.logger.Error( "Failed to parse message", @@ -252,3 +258,20 @@ func (mc *MessageCoordinator) ProcessBlock(blockHeader *types.Header, ethClient go appRelayer.ProcessHeight(block.BlockNumber, handlers, errChan) } } + +func FetchWarpMessageFromID(ethClient ethclient.Client, warpID common.Hash, blockNum *big.Int) (*relayerTypes.WarpMessageInfo, error) { + logs, err := ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{ + Topics: [][]common.Hash{{relayerTypes.WarpPrecompileLogFilter}, nil, {warpID}}, + Addresses: []common.Address{warp.ContractAddress}, + FromBlock: blockNum, + ToBlock: blockNum, + }) + if err != nil { + return nil, fmt.Errorf("could not fetch logs: %w", err) + } + if len(logs) != 1 { + return nil, fmt.Errorf("found more than 1 log: %d", len(logs)) + } + + return relayerTypes.NewWarpMessageInfo(logs[0]) +} diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 98595dcd..872af199 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -21,9 +21,7 @@ const ( warpGenesisFile = "./tests/utils/warp-genesis.json" ) -var ( - localNetworkInstance *local.LocalNetwork -) +var localNetworkInstance *local.LocalNetwork func TestE2E(t *testing.T) { if os.Getenv("RUN_E2E") == "" { @@ -77,7 +75,7 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { ginkgo.It("Batch Message", func() { BatchRelay(localNetworkInstance) }) - ginkgo.It("Relay Message API", func() { + ginkgo.FIt("Relay Message API", func() { RelayMessageAPI(localNetworkInstance) }) ginkgo.It("Warp API", func() { diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index d71342a9..435c64b2 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -8,9 +8,11 @@ import ( "context" "encoding/json" "fmt" + "io" "net/http" "time" + "github.com/ava-labs/avalanchego/ids" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/awm-relayer/api" testUtils "github.com/ava-labs/awm-relayer/tests/utils" @@ -19,6 +21,7 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ava-labs/teleporter/tests/interfaces" "github.com/ava-labs/teleporter/tests/utils" + teleporterTestUtils "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" @@ -40,7 +43,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { testUtils.FundRelayers(ctx, []interfaces.SubnetTestInfo{subnetAInfo, subnetBInfo}, fundedKey, relayerKey) log.Info("Sending teleporter message") - receipt, _, _ := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) + receipt, _, teleporterMessageID := testUtils.SendBasicTeleporterMessage(ctx, subnetAInfo, subnetBInfo, fundedKey, fundedAddress) warpMessage := getWarpMessageFromLog(ctx, receipt, subnetAInfo) // Set up relayer config @@ -89,6 +92,20 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { res, err := client.Do(req) Expect(err).Should(BeNil()) Expect(res.Status).Should(Equal("200 OK")) + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + Expect(err).Should(BeNil()) + + var response api.RelayMessageResponse + err = json.Unmarshal(body, &response) + Expect(err).Should(BeNil()) + + receipt, err := subnetBInfo.RPCClient.TransactionReceipt(ctx, common.HexToHash(response.TransactionHash)) + Expect(err).Should(BeNil()) + receiveEvent, err := teleporterTestUtils.GetEventFromLogs(receipt.Logs, subnetBInfo.TeleporterMessenger.ParseReceiveCrossChainMessage) + Expect(err).Should(BeNil()) + Expect(ids.ID(receiveEvent.MessageID)).Should(Equal(teleporterMessageID)) } // Send the same request to ensure the correct response. @@ -104,6 +121,15 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { res, err := client.Do(req) Expect(err).Should(BeNil()) Expect(res.Status).Should(Equal("200 OK")) + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + Expect(err).Should(BeNil()) + + var response api.RelayMessageResponse + err = json.Unmarshal(body, &response) + Expect(err).Should(BeNil()) + Expect(response.TransactionHash).Should(Equal("0x0000000000000000000000000000000000000000000000000000000000000000")) } // Cancel the command and stop the relayer diff --git a/types/types.go b/types/types.go index 83f53777..b71a6805 100644 --- a/types/types.go +++ b/types/types.go @@ -6,8 +6,6 @@ package types import ( "context" "errors" - "fmt" - "math/big" avalancheWarp "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/awm-relayer/utils" @@ -18,8 +16,10 @@ import ( "github.com/ethereum/go-ethereum/common" ) -var WarpPrecompileLogFilter = warp.WarpABI.Events["SendWarpMessage"].ID -var ErrInvalidLog = errors.New("invalid warp message log") +var ( + WarpPrecompileLogFilter = warp.WarpABI.Events["SendWarpMessage"].ID + 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 forwarded to the Listener to process. @@ -108,20 +108,3 @@ func UnpackWarpMessage(unsignedMsgBytes []byte) (*avalancheWarp.UnsignedMessage, } return unsignedMsg, nil } - -func FetchWarpMessageFromID(ethClient ethclient.Client, warpID common.Hash, blockNum *big.Int) (*WarpMessageInfo, error) { - logs, err := ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{ - Topics: [][]common.Hash{{WarpPrecompileLogFilter}, nil, {warpID}}, - Addresses: []common.Address{warp.ContractAddress}, - FromBlock: blockNum, - ToBlock: blockNum, - }) - if err != nil { - return nil, fmt.Errorf("could not fetch logs: %w", err) - } - if len(logs) != 1 { - return nil, fmt.Errorf("found more than 1 log: %d", len(logs)) - } - - return NewWarpMessageInfo(logs[0]) -} From 1d823f5e9fec46aaabcc158b6f065817e2af2439 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 12:51:28 -0400 Subject: [PATCH 41/56] fix --- tests/e2e_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_test.go b/tests/e2e_test.go index 872af199..be3b9768 100644 --- a/tests/e2e_test.go +++ b/tests/e2e_test.go @@ -75,7 +75,7 @@ var _ = ginkgo.Describe("[AWM Relayer Integration Tests", func() { ginkgo.It("Batch Message", func() { BatchRelay(localNetworkInstance) }) - ginkgo.FIt("Relay Message API", func() { + ginkgo.It("Relay Message API", func() { RelayMessageAPI(localNetworkInstance) }) ginkgo.It("Warp API", func() { From dba6fbe4a763b6718e1f31b0be856a892bf36a60 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 16:59:32 -0400 Subject: [PATCH 42/56] Documentation --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++ api/relay_message.go | 8 +++----- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 64a0380a..73d2682e 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,54 @@ The relayer consists of the following components: +### API + +#### `/relay` +- Used to manually relay a warp message. The body of the request must contain the following JSON: +```json +{ + "blockchain-id": "", + "message-id": "", + "block-num": +} +``` +- If successful, the endpoint will return the following JSON: +```json +{ + "transaction-hash": "" +} +``` + +#### `/relay/message` +- Used to manually relay a warp message. The body of the request must contain the following JSON: +```json +{ + "unsigned-message-bytes": "", + "source-address": "" +} +``` +- If successful, the endpoint will return the following JSON: +```json +{ + "transaction-hash": "", +} +``` + +#### `/health` +- Takes no arguments. Returns a `200` status code if all Application Relayers are healthy. Returns a `503` status if any of the Application Relayers have experienced an unrecoverable error. Here is an example return body: +```json +{ + "status": "down", + "details": { + "relayers-all": { + "status": "down", + "timestamp": "2024-06-01T05:06:07.685522Z", + "error": "" + } + } +} +``` + ## Testing ### Unit Tests diff --git a/api/relay_message.go b/api/relay_message.go index bb1c3271..5ecefdb8 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -34,8 +34,8 @@ type RelayMessageResponse struct { // Defines a manual warp message to be sent from the relayer through the API. type ManualWarpMessageRequest struct { - UnsignedMessageBytes []byte - SourceAddress common.Address + UnsignedMessageBytes []byte `json:"unsigned-message-bytes"` + SourceAddress string `json:"source-address"` } func HandleRelayMessage(messageCoordinator *relayer.MessageCoordinator) { @@ -49,7 +49,6 @@ func HandleRelay(messageCoordinator *relayer.MessageCoordinator) { func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req ManualWarpMessageRequest - err := json.NewDecoder(r.Body).Decode(&req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) @@ -63,7 +62,7 @@ func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http } warpMessageInfo := &relayerTypes.WarpMessageInfo{ - SourceAddress: req.SourceAddress, + SourceAddress: common.HexToAddress(req.SourceAddress), UnsignedMessage: unsignedMessage, } @@ -90,7 +89,6 @@ func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http func relayAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req RelayMessageRequest - err := json.NewDecoder(r.Body).Decode(&req) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) From 3ae8bf4108ef5ebd7d53cc71cb9df7a2e1e28603 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 17:00:48 -0400 Subject: [PATCH 43/56] Fix test --- tests/manual_message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/manual_message.go b/tests/manual_message.go index 53452dfc..63a4a214 100644 --- a/tests/manual_message.go +++ b/tests/manual_message.go @@ -103,7 +103,7 @@ func ManualMessage(network interfaces.LocalNetwork) { reqBody := api.ManualWarpMessageRequest{ UnsignedMessageBytes: unsignedMessage.Bytes(), - SourceAddress: offchainregistry.OffChainRegistrySourceAddress, + SourceAddress: offchainregistry.OffChainRegistrySourceAddress.Hex(), } client := http.Client{ From 7694982d2c5fdb42a36b2505143f43b4bee9fe11 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Thu, 27 Jun 2024 17:15:45 -0400 Subject: [PATCH 44/56] Simplify API --- api/relay_message.go | 2 +- relayer/message_coordinator.go | 32 +++++++------------------------- 2 files changed, 8 insertions(+), 26 deletions(-) diff --git a/api/relay_message.go b/api/relay_message.go index 5ecefdb8..ea3d44c6 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -66,7 +66,7 @@ func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http UnsignedMessage: unsignedMessage, } - txHash, err := messageCoordinator.ProcessManualWarpMessage(warpMessageInfo) + txHash, err := messageCoordinator.ProcessWarpMessage(warpMessageInfo) if err != nil { http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) return diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index 6c3f8455..f889e9bf 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -168,24 +168,20 @@ func (mc *MessageCoordinator) getApplicationRelayer( return nil } -func (mc *MessageCoordinator) ProcessManualWarpMessage( - warpMessage *relayerTypes.WarpMessageInfo, -) (common.Hash, error) { - // Send any messages that were specified in the configuration - mc.logger.Info( - "Relaying manual Warp message", - zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), - zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), - ) +func (mc *MessageCoordinator) ProcessWarpMessage(warpMessage *relayerTypes.WarpMessageInfo) (common.Hash, error) { appRelayer, handler, err := mc.getAppRelayerMessageHandler(warpMessage) if err != nil { mc.logger.Error( - "Failed to parse manual Warp message.", + "Failed to parse Warp message.", zap.Error(err), zap.String("warpMessageID", warpMessage.UnsignedMessage.ID().String()), ) return common.Hash{}, err } + if appRelayer == nil { + mc.logger.Error("Application relayer not found") + return common.Hash{}, errors.New("application relayer not found") + } return appRelayer.ProcessMessage(handler) } @@ -203,21 +199,7 @@ func (mc *MessageCoordinator) ProcessMessageID(blockchainID ids.ID, messageID co return common.Hash{}, fmt.Errorf("could not fetch warp message from ID: %w", err) } - appRelayer, handler, err := mc.getAppRelayerMessageHandler(warpMessage) - if err != nil { - mc.logger.Error( - "Failed to parse message", - zap.String("blockchainID", warpMessage.UnsignedMessage.SourceChainID.String()), - zap.Error(err), - ) - return common.Hash{}, fmt.Errorf("error getting application relayer: %w", err) - } - if appRelayer == nil { - mc.logger.Error("Application relayer not found") - return common.Hash{}, errors.New("application relayer not found") - } - - return appRelayer.ProcessMessage(handler) + return mc.ProcessWarpMessage(warpMessage) } // Meant to be ran asynchronously. Errors should be sent to errChan. From 570953c69583d01216b35e853915702fe70384df Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:02:50 -0400 Subject: [PATCH 45/56] Update api/relay_message.go Co-authored-by: F. Eugene Aumson Signed-off-by: Geoff Stuart --- api/relay_message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/relay_message.go b/api/relay_message.go index ea3d44c6..78c3de5e 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -78,7 +78,7 @@ func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http }, ) if err != nil { - http.Error(w, "error writing response: "+err.Error(), http.StatusInternalServerError) + http.Error(w, "error marshaling response: "+err.Error(), http.StatusInternalServerError) return } From 24e7694a5d63e590eaf9aa682af32a8b2c838ee4 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:02:58 -0400 Subject: [PATCH 46/56] Update README.md Co-authored-by: F. Eugene Aumson Signed-off-by: Geoff Stuart --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73d2682e..e451ab65 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ The relayer consists of the following components: "relayers-all": { "status": "down", "timestamp": "2024-06-01T05:06:07.685522Z", - "error": "" + "error": "" } } } From abfb84ef333d88926847bc0ad80fb8cf9f5bccf3 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:03:08 -0400 Subject: [PATCH 47/56] Update api/relay_message.go Co-authored-by: F. Eugene Aumson Signed-off-by: Geoff Stuart --- api/relay_message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/relay_message.go b/api/relay_message.go index 78c3de5e..00afbecc 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -119,7 +119,7 @@ func relayAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handle }, ) if err != nil { - http.Error(w, "error writing response: "+err.Error(), http.StatusInternalServerError) + http.Error(w, "error marshalling response: "+err.Error(), http.StatusInternalServerError) return } From 6995b640092a9501d612f7e10cf410f76c7b810d Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:03:20 -0400 Subject: [PATCH 48/56] Update README.md Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Signed-off-by: Geoff Stuart --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e451ab65..b524a922 100644 --- a/README.md +++ b/README.md @@ -345,7 +345,7 @@ The relayer consists of the following components: - If successful, the endpoint will return the following JSON: ```json { - "transaction-hash": "", + "transaction-hash": "", } ``` From 713a80a04aa595c8d7e119a0796ffcb9e87f6cca Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:03:29 -0400 Subject: [PATCH 49/56] Update README.md Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Signed-off-by: Geoff Stuart --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b524a922..0bfdf84d 100644 --- a/README.md +++ b/README.md @@ -338,7 +338,7 @@ The relayer consists of the following components: - Used to manually relay a warp message. The body of the request must contain the following JSON: ```json { - "unsigned-message-bytes": "", + "unsigned-message-bytes": "", "source-address": "" } ``` From a68f7a43a2af2ab0abd5524e54dd5db83fd28853 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:03:36 -0400 Subject: [PATCH 50/56] Update README.md Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Signed-off-by: Geoff Stuart --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0bfdf84d..a0ddf416 100644 --- a/README.md +++ b/README.md @@ -319,7 +319,7 @@ The relayer consists of the following components: ### API #### `/relay` -- Used to manually relay a warp message. The body of the request must contain the following JSON: +- Used to manually relay a Warp message. The body of the request must contain the following JSON: ```json { "blockchain-id": "", From 4ade9216ace5d4165aac764d8ba8a1691fb20c02 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:03:48 -0400 Subject: [PATCH 51/56] Update README.md Co-authored-by: cam-schultz <78878559+cam-schultz@users.noreply.github.com> Signed-off-by: Geoff Stuart --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0ddf416..28844874 100644 --- a/README.md +++ b/README.md @@ -323,7 +323,7 @@ The relayer consists of the following components: ```json { "blockchain-id": "", - "message-id": "", + "message-id": "", "block-num": } ``` From 54682393a7d011f42dc3953254d5568914739445 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:58:06 -0400 Subject: [PATCH 52/56] Review fixes --- README.md | 2 +- api/relay_message.go | 45 +++++++++++++++++++++++++--------- config/config.go | 2 +- main/main.go | 11 ++++----- relayer/message_coordinator.go | 8 +++--- 5 files changed, 44 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 28844874..f43f5b62 100644 --- a/README.md +++ b/README.md @@ -324,7 +324,7 @@ The relayer consists of the following components: { "blockchain-id": "", "message-id": "", - "block-num": + "block-num": "" } ``` - If successful, the endpoint will return the following JSON: diff --git a/api/relay_message.go b/api/relay_message.go index 00afbecc..59f7e878 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -5,12 +5,13 @@ import ( "math/big" "net/http" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" "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/avalanchego/ids" "github.com/ethereum/go-ethereum/common" + "go.uber.org/zap" ) const ( @@ -21,7 +22,7 @@ const ( type RelayMessageRequest struct { // Required. cb58 encoding of the source blockchain ID for the message BlockchainID string `json:"blockchain-id"` - // Required. Hex encoding of the warp message ID + // Required. cb58 encoding of the warp message ID MessageID string `json:"message-id"` // Required. Integer representation of the block number that the message was sent in BlockNum string `json:"block-num"` @@ -38,25 +39,27 @@ type ManualWarpMessageRequest struct { SourceAddress string `json:"source-address"` } -func HandleRelayMessage(messageCoordinator *relayer.MessageCoordinator) { - http.Handle(RelayAPIPath, relayAPIHandler(messageCoordinator)) +func HandleRelayMessage(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) { + http.Handle(RelayAPIPath, relayAPIHandler(logger, messageCoordinator)) } -func HandleRelay(messageCoordinator *relayer.MessageCoordinator) { - http.Handle(RelayMessageAPIPath, relayMessageAPIHandler(messageCoordinator)) +func HandleRelay(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) { + http.Handle(RelayMessageAPIPath, relayMessageAPIHandler(logger, messageCoordinator)) } -func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { +func relayMessageAPIHandler(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req ManualWarpMessageRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { + logger.Warn("could not decode request body") http.Error(w, err.Error(), http.StatusBadRequest) return } unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes) if err != nil { + logger.Warn("error unpacking warp message", zap.Error(err)) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -68,6 +71,7 @@ func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http txHash, err := messageCoordinator.ProcessWarpMessage(warpMessageInfo) if err != nil { + logger.Error("error processing message", zap.Error(err)) http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) return } @@ -78,37 +82,50 @@ func relayMessageAPIHandler(messageCoordinator *relayer.MessageCoordinator) http }, ) if err != nil { + logger.Error("error marshaling response", zap.Error(err)) http.Error(w, "error marshaling response: "+err.Error(), http.StatusInternalServerError) return } - _, _ = w.Write(resp) + _, err = w.Write(resp) + if err != nil { + logger.Error("error writing response", zap.Error(err)) + } }) } -func relayAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handler { +func relayAPIHandler(logger logging.Logger, messageCoordinator *relayer.MessageCoordinator) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var req RelayMessageRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { + logger.Warn("could not decode request body") http.Error(w, err.Error(), http.StatusBadRequest) return } blockchainID, err := ids.FromString(req.BlockchainID) if err != nil { + logger.Warn("invalid blockchainID", zap.String("blockchainID", req.BlockchainID)) http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) return } - messageID := common.HexToHash(req.MessageID) + messageID, err := ids.FromString(req.MessageID) + if err != nil { + logger.Warn("invalid messageID", zap.String("messageID", req.MessageID)) + http.Error(w, "invalid messageID: "+err.Error(), http.StatusBadRequest) + return + } blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) if !ok { + logger.Warn("invalid blockNum", zap.String("blockNum", req.BlockNum)) http.Error(w, "invalid blockNum", http.StatusBadRequest) return } txHash, err := messageCoordinator.ProcessMessageID(blockchainID, messageID, blockNum) if err != nil { + logger.Error("error processing message", zap.Error(err)) http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) return } @@ -119,10 +136,14 @@ func relayAPIHandler(messageCoordinator *relayer.MessageCoordinator) http.Handle }, ) if err != nil { + logger.Error("error marshalling response", zap.Error(err)) http.Error(w, "error marshalling response: "+err.Error(), http.StatusInternalServerError) return } - _, _ = w.Write(resp) + _, err = w.Write(resp) + if err != nil { + logger.Error("error writing response", zap.Error(err)) + } }) } diff --git a/config/config.go b/config/config.go index 590c7527..4712feaa 100644 --- a/config/config.go +++ b/config/config.go @@ -70,7 +70,7 @@ func DisplayUsageText() { // Validates the configuration // Does not modify the public fields as derived from the configuration passed to the application, -// but does initialize private fields available through getters +// but does initialize private fields available through getters. func (c *Config) Validate() error { if len(c.SourceBlockchains) == 0 { return errors.New("relayer not configured to relay from any subnets. A list of source subnets must be provided in the configuration file") diff --git a/main/main.go b/main/main.go index 6d3105c4..5b060c22 100644 --- a/main/main.go +++ b/main/main.go @@ -11,10 +11,6 @@ import ( "os" "strings" - "github.com/ava-labs/awm-relayer/messages" - offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" - "github.com/ava-labs/awm-relayer/messages/teleporter" - "github.com/ava-labs/avalanchego/api/metrics" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/message" @@ -23,6 +19,9 @@ import ( "github.com/ava-labs/awm-relayer/api" "github.com/ava-labs/awm-relayer/config" "github.com/ava-labs/awm-relayer/database" + "github.com/ava-labs/awm-relayer/messages" + 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/peers" "github.com/ava-labs/awm-relayer/relayer" "github.com/ava-labs/awm-relayer/utils" @@ -202,8 +201,8 @@ func main() { // Each Listener goroutine will have an atomic bool that it can set to false to indicate an unrecoverable error api.HandleHealthCheck(relayerHealth) - api.HandleRelay(messageCoordinator) - api.HandleRelayMessage(messageCoordinator) + api.HandleRelay(logger, messageCoordinator) + api.HandleRelayMessage(logger, messageCoordinator) // start the health check server go func() { diff --git a/relayer/message_coordinator.go b/relayer/message_coordinator.go index f889e9bf..49b2c37d 100644 --- a/relayer/message_coordinator.go +++ b/relayer/message_coordinator.go @@ -186,14 +186,14 @@ func (mc *MessageCoordinator) ProcessWarpMessage(warpMessage *relayerTypes.WarpM return appRelayer.ProcessMessage(handler) } -func (mc *MessageCoordinator) ProcessMessageID(blockchainID ids.ID, messageID common.Hash, blockNum *big.Int) (common.Hash, error) { +func (mc *MessageCoordinator) ProcessMessageID(blockchainID ids.ID, messageID ids.ID, blockNum *big.Int) (common.Hash, error) { ethClient, ok := mc.sourceClients[blockchainID] if !ok { mc.logger.Error("Source client not found", zap.String("blockchainID", blockchainID.String())) return common.Hash{}, fmt.Errorf("source client not set for blockchain: %s", blockchainID.String()) } - warpMessage, err := FetchWarpMessageFromID(ethClient, messageID, blockNum) + warpMessage, err := FetchWarpMessage(ethClient, messageID, blockNum) if err != nil { mc.logger.Error("Failed to fetch warp from blockchain", zap.String("blockchainID", blockchainID.String()), zap.Error(err)) return common.Hash{}, fmt.Errorf("could not fetch warp message from ID: %w", err) @@ -241,9 +241,9 @@ func (mc *MessageCoordinator) ProcessBlock(blockHeader *types.Header, ethClient } } -func FetchWarpMessageFromID(ethClient ethclient.Client, warpID common.Hash, blockNum *big.Int) (*relayerTypes.WarpMessageInfo, error) { +func FetchWarpMessage(ethClient ethclient.Client, warpID ids.ID, blockNum *big.Int) (*relayerTypes.WarpMessageInfo, error) { logs, err := ethClient.FilterLogs(context.Background(), interfaces.FilterQuery{ - Topics: [][]common.Hash{{relayerTypes.WarpPrecompileLogFilter}, nil, {warpID}}, + Topics: [][]common.Hash{{relayerTypes.WarpPrecompileLogFilter}, nil, {common.Hash(warpID)}}, Addresses: []common.Address{warp.ContractAddress}, FromBlock: blockNum, ToBlock: blockNum, From 349e6c3f58e4f0502773df2c3e929d873416e628 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 11:59:18 -0400 Subject: [PATCH 53/56] Fix test --- tests/relay_message_api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index 435c64b2..c9b7310f 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -69,7 +69,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { reqBody := api.RelayMessageRequest{ BlockchainID: subnetAInfo.BlockchainID.String(), - MessageID: warpMessage.ID().Hex(), + MessageID: warpMessage.ID().String(), BlockNum: receipt.BlockNumber.String(), } From 343651b3e0bcaae52c09ec0274eee8856a226194 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 13:47:26 -0400 Subject: [PATCH 54/56] review fixes --- README.md | 4 ++-- api/health_check.go | 9 ++++++--- api/relay_message.go | 12 +++--------- main/main.go | 2 +- tests/relay_message_api.go | 2 +- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index f43f5b62..14dcb18d 100644 --- a/README.md +++ b/README.md @@ -323,8 +323,8 @@ The relayer consists of the following components: ```json { "blockchain-id": "", - "message-id": "", - "block-num": "" + "message-id": "", + "block-num": "" } ``` - If successful, the endpoint will return the following JSON: diff --git a/api/health_check.go b/api/health_check.go index 10feaf56..2d6755de 100644 --- a/api/health_check.go +++ b/api/health_check.go @@ -7,16 +7,18 @@ import ( "github.com/alexliesenfeld/health" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" "go.uber.org/atomic" + "go.uber.org/zap" ) const HealthAPIPath = "/health" -func HandleHealthCheck(relayerHealth map[ids.ID]*atomic.Bool) { - http.Handle(HealthAPIPath, healthCheckHandler(relayerHealth)) +func HandleHealthCheck(logger logging.Logger, relayerHealth map[ids.ID]*atomic.Bool) { + http.Handle(HealthAPIPath, healthCheckHandler(logger, relayerHealth)) } -func healthCheckHandler(relayerHealth map[ids.ID]*atomic.Bool) http.Handler { +func healthCheckHandler(logger logging.Logger, relayerHealth map[ids.ID]*atomic.Bool) http.Handler { return health.NewHandler(health.NewChecker( health.WithCheck(health.Check{ Name: "relayers-all", @@ -30,6 +32,7 @@ func healthCheckHandler(relayerHealth map[ids.ID]*atomic.Bool) http.Handler { } if len(unhealthyRelayers) > 0 { + logger.Fatal("relayers are unhealthy for blockchains", zap.Strings("blockchains", unhealthyRelayers)) return fmt.Errorf("relayers are unhealthy for blockchains %v", unhealthyRelayers) } return nil diff --git a/api/relay_message.go b/api/relay_message.go index 59f7e878..4633c16e 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -24,8 +24,8 @@ type RelayMessageRequest struct { BlockchainID string `json:"blockchain-id"` // Required. cb58 encoding of the warp message ID MessageID string `json:"message-id"` - // Required. Integer representation of the block number that the message was sent in - BlockNum string `json:"block-num"` + // Required. Block number that the message was sent in + BlockNum uint64 `json:"block-num"` } type RelayMessageResponse struct { @@ -116,14 +116,8 @@ func relayAPIHandler(logger logging.Logger, messageCoordinator *relayer.MessageC http.Error(w, "invalid messageID: "+err.Error(), http.StatusBadRequest) return } - blockNum, ok := new(big.Int).SetString(req.BlockNum, 10) - if !ok { - logger.Warn("invalid blockNum", zap.String("blockNum", req.BlockNum)) - http.Error(w, "invalid blockNum", http.StatusBadRequest) - return - } - txHash, err := messageCoordinator.ProcessMessageID(blockchainID, messageID, blockNum) + txHash, err := messageCoordinator.ProcessMessageID(blockchainID, messageID, new(big.Int).SetUint64(req.BlockNum)) if err != nil { logger.Error("error processing message", zap.Error(err)) http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) diff --git a/main/main.go b/main/main.go index 5b060c22..0ae77aa0 100644 --- a/main/main.go +++ b/main/main.go @@ -200,7 +200,7 @@ func main() { messageCoordinator := relayer.NewMessageCoordinator(logger, messageHandlerFactories, applicationRelayers, sourceClients) // Each Listener goroutine will have an atomic bool that it can set to false to indicate an unrecoverable error - api.HandleHealthCheck(relayerHealth) + api.HandleHealthCheck(logger, relayerHealth) api.HandleRelay(logger, messageCoordinator) api.HandleRelayMessage(logger, messageCoordinator) diff --git a/tests/relay_message_api.go b/tests/relay_message_api.go index c9b7310f..bdb5852b 100644 --- a/tests/relay_message_api.go +++ b/tests/relay_message_api.go @@ -70,7 +70,7 @@ func RelayMessageAPI(network interfaces.LocalNetwork) { reqBody := api.RelayMessageRequest{ BlockchainID: subnetAInfo.BlockchainID.String(), MessageID: warpMessage.ID().String(), - BlockNum: receipt.BlockNumber.String(), + BlockNum: receipt.BlockNumber.Uint64(), } client := http.Client{ From b4376e2919c9c66f928cb3ce1acb839748f08928 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 15:40:18 -0400 Subject: [PATCH 55/56] review fixes --- api/relay_message.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/relay_message.go b/api/relay_message.go index 4633c16e..2364d281 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -29,7 +29,7 @@ type RelayMessageRequest struct { } type RelayMessageResponse struct { - // hex encoding of the source blockchain ID for the message + // hex encoding of the transaction hash containing the processed message TransactionHash string `json:"transaction-hash"` } From f33e56f25802224cef4e103162e42b163d5b8e11 Mon Sep 17 00:00:00 2001 From: Geoff Stuart Date: Tue, 2 Jul 2024 15:46:02 -0400 Subject: [PATCH 56/56] Capitalize log messages --- api/health_check.go | 2 +- api/relay_message.go | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/api/health_check.go b/api/health_check.go index 2d6755de..1fafe3d8 100644 --- a/api/health_check.go +++ b/api/health_check.go @@ -32,7 +32,7 @@ func healthCheckHandler(logger logging.Logger, relayerHealth map[ids.ID]*atomic. } if len(unhealthyRelayers) > 0 { - logger.Fatal("relayers are unhealthy for blockchains", zap.Strings("blockchains", unhealthyRelayers)) + logger.Fatal("Relayers are unhealthy for blockchains", zap.Strings("blockchains", unhealthyRelayers)) return fmt.Errorf("relayers are unhealthy for blockchains %v", unhealthyRelayers) } return nil diff --git a/api/relay_message.go b/api/relay_message.go index 2364d281..164b2426 100644 --- a/api/relay_message.go +++ b/api/relay_message.go @@ -52,14 +52,14 @@ func relayMessageAPIHandler(logger logging.Logger, messageCoordinator *relayer.M var req ManualWarpMessageRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { - logger.Warn("could not decode request body") + logger.Warn("Could not decode request body") http.Error(w, err.Error(), http.StatusBadRequest) return } unsignedMessage, err := types.UnpackWarpMessage(req.UnsignedMessageBytes) if err != nil { - logger.Warn("error unpacking warp message", zap.Error(err)) + logger.Warn("Error unpacking warp message", zap.Error(err)) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -71,7 +71,7 @@ func relayMessageAPIHandler(logger logging.Logger, messageCoordinator *relayer.M txHash, err := messageCoordinator.ProcessWarpMessage(warpMessageInfo) if err != nil { - logger.Error("error processing message", zap.Error(err)) + logger.Error("Error processing message", zap.Error(err)) http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) return } @@ -82,14 +82,14 @@ func relayMessageAPIHandler(logger logging.Logger, messageCoordinator *relayer.M }, ) if err != nil { - logger.Error("error marshaling response", zap.Error(err)) + logger.Error("Error marshaling response", zap.Error(err)) http.Error(w, "error marshaling response: "+err.Error(), http.StatusInternalServerError) return } _, err = w.Write(resp) if err != nil { - logger.Error("error writing response", zap.Error(err)) + logger.Error("Error writing response", zap.Error(err)) } }) } @@ -99,27 +99,27 @@ func relayAPIHandler(logger logging.Logger, messageCoordinator *relayer.MessageC var req RelayMessageRequest err := json.NewDecoder(r.Body).Decode(&req) if err != nil { - logger.Warn("could not decode request body") + logger.Warn("Could not decode request body") http.Error(w, err.Error(), http.StatusBadRequest) return } blockchainID, err := ids.FromString(req.BlockchainID) if err != nil { - logger.Warn("invalid blockchainID", zap.String("blockchainID", req.BlockchainID)) + logger.Warn("Invalid blockchainID", zap.String("blockchainID", req.BlockchainID)) http.Error(w, "invalid blockchainID: "+err.Error(), http.StatusBadRequest) return } messageID, err := ids.FromString(req.MessageID) if err != nil { - logger.Warn("invalid messageID", zap.String("messageID", req.MessageID)) + logger.Warn("Invalid messageID", zap.String("messageID", req.MessageID)) http.Error(w, "invalid messageID: "+err.Error(), http.StatusBadRequest) return } txHash, err := messageCoordinator.ProcessMessageID(blockchainID, messageID, new(big.Int).SetUint64(req.BlockNum)) if err != nil { - logger.Error("error processing message", zap.Error(err)) + logger.Error("Error processing message", zap.Error(err)) http.Error(w, "error processing message: "+err.Error(), http.StatusInternalServerError) return } @@ -130,14 +130,14 @@ func relayAPIHandler(logger logging.Logger, messageCoordinator *relayer.MessageC }, ) if err != nil { - logger.Error("error marshalling response", zap.Error(err)) + logger.Error("Error marshalling response", zap.Error(err)) http.Error(w, "error marshalling response: "+err.Error(), http.StatusInternalServerError) return } _, err = w.Write(resp) if err != nil { - logger.Error("error writing response", zap.Error(err)) + logger.Error("Error writing response", zap.Error(err)) } }) }