Skip to content

Commit

Permalink
Add TestSmokeRPCCompatibilities for RPC compatibility smoke test (#298)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Kourin1996 authored Dec 17, 2024
1 parent e49dd9e commit e81d62e
Show file tree
Hide file tree
Showing 2 changed files with 224 additions and 18 deletions.
36 changes: 18 additions & 18 deletions compat_test/compat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
})
Expand All @@ -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
})
Expand All @@ -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
})
Expand All @@ -776,23 +776,23 @@ 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
})

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
})

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
Expand All @@ -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
Expand All @@ -811,15 +811,15 @@ 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
})

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
})
Expand Down Expand Up @@ -854,15 +854,15 @@ 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
})

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
})
Expand All @@ -889,15 +889,15 @@ 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
})

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
})
Expand Down
206 changes: 206 additions & 0 deletions compat_test/smoke_compat_test.go
Original file line number Diff line number Diff line change
@@ -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
})
}

0 comments on commit e81d62e

Please sign in to comment.