From e81d62ece3f2a95269d426c115a46f55a190e5f1 Mon Sep 17 00:00:00 2001 From: kourin Date: Tue, 17 Dec 2024 23:50:47 +0900 Subject: [PATCH] Add TestSmokeRPCCompatibilities for RPC compatibility smoke test (#298) * Add TestSmokeRPCCompatibilities for RPC compatibility smoke test * Fix upper bound of random value in TestSmokeRPCCompatibilities * Add reset totalDifficulty of L1 block in TestSmokeRPCCompatibilities * Fix comment * Fix comment * Revert incorrect reset of totalDifficulty for L1 block data in TestSmokeRPCCompatibilities * Add codes to make sure block 0 and 1 are tested in TestSmokeRPCCompatibilities --- compat_test/compat_test.go | 36 +++--- compat_test/smoke_compat_test.go | 206 +++++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+), 18 deletions(-) create mode 100644 compat_test/smoke_compat_test.go diff --git a/compat_test/compat_test.go b/compat_test/compat_test.go index 164ea96aae..92b63cb336 100644 --- a/compat_test/compat_test.go +++ b/compat_test/compat_test.go @@ -135,7 +135,7 @@ func TestCompatibilityOfChains(t *testing.T) { for i := startBlock; i <= endBlock; i++ { index := i g.Go(func() error { - err := fetchBlockElements(clients, index, resultChan) + err := fetchBlockElements(longCtx, clients, index, resultChan) if err != nil { fmt.Printf("block %d err: %v\n", index, err) } @@ -700,8 +700,8 @@ func makeTransactionsComparable(txs []*types.Transaction) { } } -func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *blockResults) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Minute) +func fetchBlockElements(ctx context.Context, clients *clients, blockNumber uint64, resultChan chan *blockResults) error { + ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() blockNum := big.NewInt(int64(blockNumber)) blockNumberHex := hexutil.EncodeUint64(blockNumber) @@ -735,7 +735,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b var err error results.opBlockByHash, err = clients.opEthclient.BlockByHash(ctx, opBlockByNumber.Hash()) if err != nil { - return fmt.Errorf("%s: %w", "op.BlockByHash", err) + return fmt.Errorf("opEthclient.%s: %w", "op.BlockByHash", err) } return nil }) @@ -744,14 +744,14 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockByNumber, getBlockByNumber, blockNumberHex, true) if err != nil { - return fmt.Errorf("%s: %w", getBlockByNumber, err) + return fmt.Errorf("celoClient.%s: %w", getBlockByNumber, err) } return nil }) g.Go(func() error { err := rpcCall(ctx, clients.opClient, &results.opRawBlockByNumber, getBlockByNumber, blockNumberHex, true) if err != nil { - return fmt.Errorf("%s: %w", getBlockByNumber, err) + return fmt.Errorf("opClient.%s: %w", getBlockByNumber, err) } return nil }) @@ -760,14 +760,14 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockByHash, getBlockByHash, blockHash.Hex(), true) if err != nil { - return fmt.Errorf("%s: %w", getBlockByHash, err) + return fmt.Errorf("celoClient.%s: %w", getBlockByHash, err) } return nil }) g.Go(func() error { err := rpcCall(ctx, clients.opClient, &results.opRawBlockByHash, getBlockByHash, blockHash.Hex(), true) if err != nil { - return fmt.Errorf("%s: %w", getBlockByHash, err) + return fmt.Errorf("opClient.%s: %w", getBlockByHash, err) } return nil }) @@ -776,7 +776,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockReceipt, getBlockReceipt, blockHash.Hex()) if err != nil { - return fmt.Errorf("%s: %w", getBlockReceipt, err) + return fmt.Errorf("celoClient.%s: %w", getBlockReceipt, err) } return nil }) @@ -784,7 +784,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.opClient, &results.opRawBlockReceipt, getBlockReceipt, blockHash.Hex()) if err != nil { - return fmt.Errorf("%s: %w", getBlockReceipt, err) + return fmt.Errorf("opClient.%s: %w", getBlockReceipt, err) } return nil }) @@ -792,7 +792,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { celoBlockReceipts, err := clients.celoEthclient.BlockReceipts(ctx, rpc.BlockNumberOrHashWithHash(blockHash, true)) if err != nil { - return fmt.Errorf("BlockReceipts: %w", err) + return fmt.Errorf("celoEthclient.BlockReceipts: %w", err) } results.celoBlockReceipts = celoBlockReceipts return nil @@ -801,7 +801,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { opBlockReceipts, err := clients.opEthclient.BlockReceipts(ctx, rpc.BlockNumberOrHashWithHash(blockHash, true)) if err != nil { - return fmt.Errorf("BlockReceipts: %w", err) + return fmt.Errorf("opEthclient.BlockReceipts: %w", err) } results.opBlockReceipts = opBlockReceipts return nil @@ -811,7 +811,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.celoClient, &results.celoRawBlockReceipts, getBlockReceipts, blockHash.Hex()) if err != nil { - return fmt.Errorf("%s: %w", getBlockReceipts, err) + return fmt.Errorf("celoClient.%s: %w", getBlockReceipts, err) } return nil }) @@ -819,7 +819,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.opClient, &results.opRawBlockReceipts, getBlockReceipts, blockHash.Hex()) if err != nil { - return fmt.Errorf("%s: %w", getBlockReceipts, err) + return fmt.Errorf("opClient.%s: %w", getBlockReceipts, err) } return nil }) @@ -854,7 +854,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.celoClient, &results.celoRawTxs[index], getTxByHash, hexHash) if err != nil { - return fmt.Errorf("%s: %w", getTxByHash, err) + return fmt.Errorf("celoClient.%s: %w", getTxByHash, err) } return nil }) @@ -862,7 +862,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.opClient, &results.opRawTxs[index], getTxByHash, hexHash) if err != nil { - return fmt.Errorf("%s: %w", getTxByHash, err) + return fmt.Errorf("opClient.%s: %w", getTxByHash, err) } return nil }) @@ -889,7 +889,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.celoClient, &results.celoRawReceipts[index], getTransactionReceipt, hexHash) if err != nil { - return fmt.Errorf("%s: %w", getTransactionReceipt, err) + return fmt.Errorf("celoClient.%s: %w", getTransactionReceipt, err) } return nil }) @@ -897,7 +897,7 @@ func fetchBlockElements(clients *clients, blockNumber uint64, resultChan chan *b g.Go(func() error { err := rpcCall(ctx, clients.opClient, &results.opRawReceipts[index], getTransactionReceipt, hexHash) if err != nil { - return fmt.Errorf("%s: %w", getTransactionReceipt, err) + return fmt.Errorf("opClient.%s: %w", getTransactionReceipt, err) } return nil }) diff --git a/compat_test/smoke_compat_test.go b/compat_test/smoke_compat_test.go new file mode 100644 index 0000000000..6dbd617f08 --- /dev/null +++ b/compat_test/smoke_compat_test.go @@ -0,0 +1,206 @@ +//go:build compat_test + +package compat_tests + +import ( + "context" + "flag" + "math/rand" + "testing" + "time" + + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" +) + +var ( + // CLI arguments + blockInterval uint64 + enableRandomBlockTest bool + + clientOpts = []rpc.ClientOption{ + rpc.WithWebsocketMessageSizeLimit(1024 * 1024 * 256), + } +) + +func init() { + flag.Uint64Var(&blockInterval, "block-interval", 1_000_000, "number of blocks to skip between test iterations") + flag.BoolVar(&enableRandomBlockTest, "random-block", false, "enable random block height within the range during test iterations") +} + +func TestSmokeRPCCompatibilities(t *testing.T) { + // TestSmokeRPCCompatibilities checks the compatibility of Celo L1 and Celo L2 RPC responses. + // The test retrieves the following types of data at the beginning, the last L1 block, and at every one million blocks to verify compatibility. + // 1. Block + // 2. Transactions + // 3. Transaction Receipts + // 4. Block Receipts + flag.Parse() + + if celoRpcURL == "" { + t.Fatal("celo rpc url not set example usage:\n go test -v ./compat_test -tags compat_test -run TestSmokeRPCCompatibilities -celo-url ws://localhost:8546 -op-geth-url ws://localhost:9994") + } + if opGethRpcURL == "" { + t.Fatal("op-geth rpc url not set example usage:\n go test -v ./compat_test -tags compat_test -run TestSmokeRPCCompatibilities -celo-url ws://localhost:8546 -op-geth-url ws://localhost:9994") + } + if blockInterval == 0 { + t.Fatal("block interval must be positive integer:\n go test -v ./compat_test -tags compat_test -run TestSmokeRPCCompatibilities -celo-url ws://localhost:8546 -op-geth-url ws://localhost:9994 -block-interval 1000000") + } + + // Setup RPC clients for Celo L1 and Celo L2 server + celoClient, err := rpc.DialOptions(context.Background(), celoRpcURL, clientOpts...) + require.NoError(t, err) + celoEthClient := ethclient.NewClient(celoClient) + + opClient, err := rpc.DialOptions(context.Background(), opGethRpcURL, clientOpts...) + require.NoError(t, err) + opEthClient := ethclient.NewClient(opClient) + + initCtx, cancel := context.WithTimeout(context.Background(), time.Minute) + t.Cleanup(cancel) + + // Fetch Chain IDs and make sure they're same + celoChainId, err := celoEthClient.ChainID(initCtx) + require.NoError(t, err) + opChainId, err := opEthClient.ChainID(initCtx) + require.NoError(t, err) + require.Equal(t, celoChainId.Uint64(), opChainId.Uint64(), "chain ids of referenced chains differ") + + // Make sure the Chain ID is supported + chainId := celoChainId.Uint64() + _, ok := gingerbreadBlocks[chainId] + require.True(t, ok, "chain id %d not found in supported chainIDs %v", chainId, gingerbreadBlocks) + + // Fetch the last block height in Celo L1 + lastCeloL1BlockHeight, err := celoEthClient.BlockNumber(initCtx) + require.NoError(t, err) + + t.Logf("Last Celo L1 Block Height: %d", lastCeloL1BlockHeight) + + resultChan := make(chan *blockResults, 100) + globalCtx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + // Fetching RPC data job + fetchingEg, jobCtx := errgroup.WithContext(globalCtx) + fetchingEg.SetLimit(6) + + fetchingEg.Go(func() error { + clients := &clients{ + celoEthclient: celoEthClient, + opEthclient: opEthClient, + celoClient: celoClient, + opClient: opClient, + } + + // Fetch data of the first 2 blocks + asyncFetchRpcData(t, jobCtx, fetchingEg, 0, clients, resultChan) + asyncFetchRpcData(t, jobCtx, fetchingEg, 1, clients, resultChan) + + // Fetch some random blocks between blockInterval to lastCeloL1BlockHeight + for currentBlockNumber := uint64(0); currentBlockNumber < lastCeloL1BlockHeight; currentBlockNumber += blockInterval { + // exit loop if context is canceled + if isContextCanceled(jobCtx) { + t.Logf("Context canceled, exiting fetching loop at height %d", currentBlockNumber) + return nil + } + + // decide block number to fetch between [currentBlockNumber, min(currentBlockNumber+blockInterval-1, lastCeloL1BlockHeight)) + offset := uint64(0) + if enableRandomBlockTest { + randomUpperBound := blockInterval + if currentBlockNumber+randomUpperBound-1 >= lastCeloL1BlockHeight { + randomUpperBound = lastCeloL1BlockHeight - currentBlockNumber + } + + // Int63n returns a non-negative random number + offset = uint64(rand.Int63n(int64(randomUpperBound))) + } + + targetHeight := currentBlockNumber + offset + if targetHeight < 2 { + // ignore block at 0 and 1 because they're already fetched + if blockInterval == 1 { + continue + } + + targetHeight = 2 + } + + // Fetch data at the point of current range + asyncFetchRpcData(t, jobCtx, fetchingEg, targetHeight, clients, resultChan) + } + + // Fetch data at the last height + asyncFetchRpcData(t, jobCtx, fetchingEg, lastCeloL1BlockHeight, clients, resultChan) + + return nil + }) + + // Testing fetched data job + testingEg, jobCtx := errgroup.WithContext(globalCtx) + testingEg.Go(func() error { + for result := range resultChan { + if isContextCanceled(jobCtx) { + t.Logf("Context canceled, exiting testing loop at height %d", result.blockNumber) + return nil + } + + result := result + testingEg.Go(func() error { + err := result.Verify(chainId) + if err != nil { + t.Errorf("data at height %d err: %v\n", result.blockNumber, err) + + return err + } + + t.Logf("Verified data at height %d", result.blockNumber) + + return nil + }) + } + + return nil + }) + + // Wait for fetching and testing jobs to finish + fetchingEg.Wait() + close(resultChan) // close resultChan to signal testingEg to end + testingEg.Wait() +} + +func isContextCanceled(ctx context.Context) bool { + select { + case <-ctx.Done(): + return true + default: + return false + } +} + +func asyncFetchRpcData( + t *testing.T, + ctx context.Context, + eg *errgroup.Group, + blockNumber uint64, + clients *clients, + resultChan chan *blockResults, +) { + t.Helper() + + eg.Go(func() error { + err := fetchBlockElements(ctx, clients, blockNumber, resultChan) + if err != nil { + t.Errorf("failed to fetch block elements at height %d: %v", blockNumber, err) + + return err + } + + t.Logf("Fetched data at height %d", blockNumber) + + return nil + }) +}