From 8f63e123a557a9e20295eaf68bbafee71d8791f1 Mon Sep 17 00:00:00 2001 From: Leo Date: Tue, 8 Feb 2022 21:42:15 +0100 Subject: [PATCH] node/pkg/ethereum: add MessageEventsForTransaction This retrieves a single transaction's MessagePublication events. This has the same security assumptions than listening to the log events - namely, ensuring the right contract has emitted them. Tested locally with a mainnet transaction. commit-id:64347ecc --- node/hack/parse_eth_tx/parse_eth_tx.go | 59 ++++++++++++++++++ node/pkg/ethereum/by_transaction.go | 82 ++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 node/hack/parse_eth_tx/parse_eth_tx.go create mode 100644 node/pkg/ethereum/by_transaction.go diff --git a/node/hack/parse_eth_tx/parse_eth_tx.go b/node/hack/parse_eth_tx/parse_eth_tx.go new file mode 100644 index 000000000..45c52e8d2 --- /dev/null +++ b/node/hack/parse_eth_tx/parse_eth_tx.go @@ -0,0 +1,59 @@ +package main + +import ( + "context" + "flag" + "github.com/certusone/wormhole/node/pkg/ethereum" + "github.com/certusone/wormhole/node/pkg/vaa" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "log" +) + +var ( + flagEthRPC = flag.String("ethRPC", "http://localhost:8545", "Ethereum JSON-RPC endpoint") + flagContractAddr = flag.String("contractAddr", "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B", "Ethereum contract address") + flagTx = flag.String("tx", "", "Transaction to parse") +) + +func main() { + flag.Parse() + if *flagTx == "" { + log.Fatal("No transaction specified") + } + + ctx := context.Background() + + c, err := ethclient.DialContext(ctx, *flagEthRPC) + if err != nil { + log.Fatal(err) + } + + contractAddr := common.HexToAddress(*flagContractAddr) + transactionHash := common.HexToHash(*flagTx) + + msgs, err := ethereum.MessageEventsForTransaction(ctx, c, contractAddr, vaa.ChainIDEthereum, transactionHash) + if err != nil { + log.Fatal(err) + } + + for _, k := range msgs { + v := &vaa.VAA{ + Version: vaa.SupportedVAAVersion, + GuardianSetIndex: 1, + Signatures: nil, + Timestamp: k.Timestamp, + Nonce: k.Nonce, + EmitterChain: k.EmitterChain, + EmitterAddress: k.EmitterAddress, + Payload: k.Payload, + Sequence: k.Sequence, + ConsistencyLevel: k.ConsistencyLevel, + } + + log.Println("------------------------------------------------------") + log.Printf("Message ID: %s", v.MessageID()) + log.Printf("Digest: %s", v.HexDigest()) + log.Printf("VAA: %+v", v) + } +} diff --git a/node/pkg/ethereum/by_transaction.go b/node/pkg/ethereum/by_transaction.go new file mode 100644 index 000000000..eabcbf2c4 --- /dev/null +++ b/node/pkg/ethereum/by_transaction.go @@ -0,0 +1,82 @@ +package ethereum + +import ( + "context" + "fmt" + "github.com/certusone/wormhole/node/pkg/common" + "github.com/certusone/wormhole/node/pkg/ethereum/abi" + "github.com/certusone/wormhole/node/pkg/vaa" + eth_common "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethclient" + "time" +) + +var ( + // SECURITY: Hardcoded ABI identifier for the LogMessagePublished topic. When using the watcher, we don't need this + // since the node will only hand us pre-filtered events. In this case, we need to manually verify it + // since ParseLogMessagePublished will only verify whether it parses. + logMessagePublishedTopic = eth_common.HexToHash("0x6eb224fb001ed210e379b335e35efe88672a8ce935d981a6896b27ffdf52a3b2") +) + +func MessageEventsForTransaction( + ctx context.Context, + c *ethclient.Client, + contract eth_common.Address, + chainId vaa.ChainID, + tx eth_common.Hash) ([]*common.MessagePublication, error) { + + f, err := abi.NewAbiFilterer(contract, c) + if err != nil { + return nil, fmt.Errorf("failed to create ABI filterer: %w", err) + } + + // Get transactions logs from transaction + receipt, err := c.TransactionReceipt(ctx, tx) + if err != nil { + return nil, fmt.Errorf("failed to get transaction receipt: %w", err) + } + + // Get block + block, err := c.BlockByHash(ctx, receipt.BlockHash) + if err != nil { + return nil, fmt.Errorf("failed to get block: %w", err) + } + + msgs := make([]*common.MessagePublication, 0, len(receipt.Logs)) + + // Extract logs + for _, l := range receipt.Logs { + // SECURITY: Skip logs not produced by our contract. + if l.Address != contract { + continue + } + + if l == nil { + continue + } + + if l.Topics[0] != logMessagePublishedTopic { + continue + } + + ev, err := f.ParseLogMessagePublished(*l) + if err != nil { + return nil, fmt.Errorf("failed to parse log: %w", err) + } + + message := &common.MessagePublication{ + TxHash: ev.Raw.TxHash, + Timestamp: time.Unix(int64(block.Time()), 0), + Nonce: ev.Nonce, + Sequence: ev.Sequence, + EmitterChain: chainId, + EmitterAddress: PadAddress(ev.Sender), + Payload: ev.Payload, + ConsistencyLevel: ev.ConsistencyLevel, + } + + msgs = append(msgs, message) + } + + return msgs, nil +}