From e64d85df54bbfd1141d164c7cd3404c3755f56fa Mon Sep 17 00:00:00 2001 From: Bruce Riley Date: Mon, 27 Jan 2025 10:00:22 -0600 Subject: [PATCH] Make tool general purpose --- node/hack/evm_test/wstest.go | 146 ++++++++++++++++++++++++++++ node/hack/sei_evm/seievm_example.go | 65 ------------- 2 files changed, 146 insertions(+), 65 deletions(-) create mode 100644 node/hack/evm_test/wstest.go delete mode 100644 node/hack/sei_evm/seievm_example.go diff --git a/node/hack/evm_test/wstest.go b/node/hack/evm_test/wstest.go new file mode 100644 index 0000000000..adc8926f74 --- /dev/null +++ b/node/hack/evm_test/wstest.go @@ -0,0 +1,146 @@ +// This tool can be used to verify that an EVM endpoint works properly with go-ethereum websocket subscriptions. +// It can subscribe to latest blocks as well as log events from the Wormhole core contract and just logs them out. +// +// To run this tool, do: +// go run wstest.go --rpc [--contract ] [--blocks] +// +// where +// --contract` subscribes to log events from the specified Wormhole core contract +// --blocks subscribes to the latest blocks. +// +// To listen to log events from the SeiEVM test endpoint (what this was originally written for) do: +// go run wstest.go --rpc wss://evm-ws-testnet.sei-apis.com --contract 0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd + +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "go.uber.org/zap" + + ethAbi "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/ethabi" + ethBind "github.com/ethereum/go-ethereum/accounts/abi/bind" + ethCommon "github.com/ethereum/go-ethereum/common" + ethTypes "github.com/ethereum/go-ethereum/core/types" + ethClient "github.com/ethereum/go-ethereum/ethclient" + ethRpc "github.com/ethereum/go-ethereum/rpc" +) + +var ( + rpc = flag.String("rpc", "", "Websocket URL, this parameter is required") + contract = flag.String("contract", "", "Core contract address, leave blank to not subscribe to the core contract") + blocks = flag.Bool("blocks", false, "Also subscribe to new blocks, default is false") +) + +func main() { + flag.Parse() + logger, _ := zap.NewDevelopment() + if *rpc == "" { + logger.Fatal(`The "--rpc" parameter is required`) + } + if *contract == "" && !*blocks { + logger.Fatal(`Must specify either "--contract" or "--blocks" or both`) + } + + logger.Info("Connecting to websocket endpoint", zap.String("webSocket", *rpc)) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + rawClient, err := ethRpc.DialContext(ctx, *rpc) + if err != nil { + logger.Fatal("Failed to connect to RPC", zap.Error(err)) + } + + client := ethClient.NewClient(rawClient) + + errC := make(chan error) + + if *blocks { + logger.Info("Subscribing for latest blocks") + headSink := make(chan *ethTypes.Header, 2) + headerSubscription, err := client.SubscribeNewHead(ctx, headSink) + if err != nil { + logger.Fatal("Failed to subscribe to latest blocks", zap.Error(err)) + } + + go func() { + logger.Info("Waiting for latest block events") + defer headerSubscription.Unsubscribe() + for { + select { + case <-ctx.Done(): + return + case err := <-headerSubscription.Err(): + errC <- fmt.Errorf("block subscription failed: %w", err) + return + case block := <-headSink: + // These two pointers should have been checked before the event was placed on the channel, but just being safe. + if block == nil { + logger.Error("New header event is nil") + continue + } + logger.Info("Received a new block", zap.Any("block", block)) + } + } + }() + } + + if *contract != "" { + logger.Info("Subscribing to log events from contract", zap.String("contractAddr", *contract)) + filterer, err := ethAbi.NewAbiFilterer(ethCommon.BytesToAddress(ethCommon.HexToAddress(*contract).Bytes()), client) + if err != nil { + logger.Fatal("Failed to create filter", zap.Error(err)) + } + + timeout, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + messageC := make(chan *ethAbi.AbiLogMessagePublished, 2) + messageSub, err := filterer.WatchLogMessagePublished(ðBind.WatchOpts{Context: timeout}, messageC, nil) + if err != nil { + logger.Fatal("Failed to subscribe to events", zap.Error(err)) + } + defer messageSub.Unsubscribe() + + logger.Info("Waiting for log events from contract") + go func() { + for { + select { + case <-ctx.Done(): + return + case err := <-messageSub.Err(): + errC <- fmt.Errorf("message subscription failed: %w", err) + return + case ev := <-messageC: + logger.Info("Received a log event from the contract", zap.Any("ev", ev)) + } + } + }() + } + + // Wait for SIGTERM. + logger.Info("Waiting for sigterm.") + sigterm := make(chan os.Signal, 1) + signal.Notify(sigterm, syscall.SIGTERM) + go func() { + <-sigterm + logger.Info("Received sigterm. exiting.") + cancel() + }() + + // Wait for either a shutdown or a fatal error from the permissions watcher. + select { + case <-ctx.Done(): + logger.Info("Context cancelled, exiting...") + break + case err := <-errC: + logger.Error("Encountered an error, exiting", zap.Error(err)) + break + } + +} diff --git a/node/hack/sei_evm/seievm_example.go b/node/hack/sei_evm/seievm_example.go deleted file mode 100644 index a86e3f0163..0000000000 --- a/node/hack/sei_evm/seievm_example.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "context" - "time" - - "go.uber.org/zap" - - "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/ethabi" - ethAbi "github.com/certusone/wormhole/node/pkg/watchers/evm/connectors/ethabi" - ethBind "github.com/ethereum/go-ethereum/accounts/abi/bind" - ethCommon "github.com/ethereum/go-ethereum/common" - ethClient "github.com/ethereum/go-ethereum/ethclient" - ethRpc "github.com/ethereum/go-ethereum/rpc" -) - -const wsStr = "wss://evm-ws-testnet.sei-apis.com" - -// This is for the websocket proxy that we are using in testnet (but which is not robust enough to use in mainnet). -//const wsStr = "ws://localhost:8080" - -const contractAddrStr = "0xBB73cB66C26740F31d1FabDC6b7A46a038A300dd" - -func main() { - logger, _ := zap.NewDevelopment() - logger.Info("Connecting to Sei EVM", zap.String("webSocket", wsStr), zap.String("contractAddr", contractAddrStr)) - ctx := context.Background() - - rawClient, err := ethRpc.DialContext(ctx, wsStr) - if err != nil { - logger.Fatal("Failed to connect to RPC", zap.Error(err)) - } - - client := ethClient.NewClient(rawClient) - - logger.Info("Creating filter for log events from contract") - filterer, err := ethAbi.NewAbiFilterer(ethCommon.BytesToAddress(ethCommon.HexToAddress(contractAddrStr).Bytes()), client) - if err != nil { - logger.Fatal("Failed to create filter", zap.Error(err)) - } - - logger.Info("Subscribing to log events from contract") - timeout, cancel := context.WithTimeout(ctx, 15*time.Second) - defer cancel() - messageC := make(chan *ethabi.AbiLogMessagePublished, 2) - messageSub, err := filterer.WatchLogMessagePublished(ðBind.WatchOpts{Context: timeout}, messageC, nil) - if err != nil { - logger.Fatal("Failed to subscribe to events", zap.Error(err)) - } - defer messageSub.Unsubscribe() - - logger.Info("Waiting for log events from contract") - for { - select { - case <-ctx.Done(): - break - case err := <-messageSub.Err(): - logger.Error("Message subscription failed", zap.Error(err)) - break - case ev := <-messageC: - logger.Info("Received a log event from the contract", zap.Any("ev", ev)) - } - } - -}