From 92ffb4dba7f06ff693612d2b0d04b5e4cd301c25 Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Fri, 6 Mar 2026 15:50:03 +0800 Subject: [PATCH] test(ethclient): port gethclient tests from upstream (#2053) --- ethclient/gethclient/gethclient_test.go | 480 ++++++++++++++++++++++++ 1 file changed, 480 insertions(+) diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index 0a5502171a..9f8deb41bc 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -17,13 +17,398 @@ package gethclient import ( + "bytes" + "context" "encoding/json" "math/big" + "os" + "path/filepath" + "strings" "testing" + ethereum "github.com/XinFinOrg/XDPoSChain" + "github.com/XinFinOrg/XDPoSChain/XDCx" + "github.com/XinFinOrg/XDPoSChain/XDCxlending" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/ethash" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/eth" + "github.com/XinFinOrg/XDPoSChain/eth/ethconfig" + "github.com/XinFinOrg/XDPoSChain/eth/filters" + "github.com/XinFinOrg/XDPoSChain/eth/tracers" + _ "github.com/XinFinOrg/XDPoSChain/eth/tracers/native" + "github.com/XinFinOrg/XDPoSChain/ethclient" + "github.com/XinFinOrg/XDPoSChain/node" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/XinFinOrg/XDPoSChain/rpc" ) +var ( + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey) + testContract = common.HexToAddress("0xbeef") + testEmpty = common.HexToAddress("0xeeee") + testSlot = common.HexToHash("0xdeadbeef") + testValue = crypto.Keccak256Hash(testSlot[:]) + testBalance = big.NewInt(2e15) +) + +func newTestBackend(t *testing.T) (*node.Node, []*types.Block, []common.Hash) { + // Generate test chain. + genesis, blocks, txHashes := generateTestChain() + // Create node + n, err := node.New(&node.Config{ + DataDir: t.TempDir(), + HTTPModules: []string{"debug", "eth", "admin"}, + }) + if err != nil { + t.Fatalf("can't create new node: %v", err) + } + xdcxDir := filepath.Join(t.TempDir(), "xdcx") + if err := os.MkdirAll(xdcxDir, 0o755); err != nil { + t.Fatalf("can't create XDCx data dir: %v", err) + } + xdcx := XDCx.New(n, &XDCx.Config{DataDir: xdcxDir}) + lending := XDCxlending.New(n, xdcx) + // Create Ethereum Service + config := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000} + ethservice, err := eth.New(n, config, xdcx, lending) + if err != nil { + t.Fatalf("can't create new ethereum service: %v", err) + } + n.RegisterAPIs(tracers.APIs(ethservice.APIBackend)) + + filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{}) + n.RegisterAPIs([]rpc.API{{ + Namespace: "eth", + Service: filters.NewFilterAPI(filterSystem, false), + }}) + + // Import the test chain. + if err := n.Start(); err != nil { + t.Fatalf("can't start test node: %v", err) + } + if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { + t.Fatalf("can't import test blocks: %v", err) + } + return n, blocks, txHashes +} + +func generateTestChain() (*core.Genesis, []*types.Block, []common.Hash) { + chainConfig := *params.AllEthashProtocolChanges + chainConfig.Eip1559Block = big.NewInt(0) + // Ensure global chain constants match the test chain config before block generation. + common.CopyConstants(chainConfig.ChainID.Uint64()) + genesis := &core.Genesis{ + Config: &chainConfig, + Alloc: types.GenesisAlloc{ + testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}}, + testContract: {Nonce: 1, Code: []byte{0x13, 0x37}}, + testEmpty: {Balance: big.NewInt(1)}, + }, + ExtraData: []byte("test genesis"), + Timestamp: 9000, + } + txHashes := make([]common.Hash, 0) + generate := func(i int, g *core.BlockGen) { + g.OffsetTime(5) + g.SetExtra([]byte("test")) + + to := common.BytesToAddress([]byte{byte(i + 1)}) + tx := types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), + To: &to, + Value: big.NewInt(int64(2*i + 1)), + Gas: params.TxGas, + GasPrice: big.NewInt(params.InitialBaseFee), + Data: nil, + }) + tx, _ = types.SignTx(tx, types.LatestSignerForChainID(chainConfig.ChainID), testKey) + g.AddTx(tx) + txHashes = append(txHashes, tx.Hash()) + } + _, blocks, _ := core.GenerateChainWithGenesis(genesis, ethash.NewFaker(), 1, generate) + blocks = append([]*types.Block{genesis.ToBlock()}, blocks...) + return genesis, blocks, txHashes +} + +func TestGethClient(t *testing.T) { + backend, _, txHashes := newTestBackend(t) + client := backend.Attach() + defer backend.Close() + defer client.Close() + + tests := []struct { + name string + test func(t *testing.T) + }{ + { + "TestGCStats", + func(t *testing.T) { testGCStats(t, client) }, + }, { + "TestMemStats", + func(t *testing.T) { testMemStats(t, client) }, + }, { + "TestGetNodeInfo", + func(t *testing.T) { testGetNodeInfo(t, client) }, + }, { + "TestSubscribePendingTxHashes", + func(t *testing.T) { testSubscribePendingTransactions(t, client) }, + }, { + "TestCallContract", + func(t *testing.T) { testCallContract(t, client) }, + }, { + "TestCallContractWithBlockOverrides", + func(t *testing.T) { testCallContractWithBlockOverrides(t, client) }, + }, { + "TestTraceTransactionWithCallTracer", + func(t *testing.T) { testTraceTransactionWithCallTracer(t, client, txHashes) }, + }, { + "TestTraceCallWithCallTracer", + func(t *testing.T) { testTraceCallWithCallTracer(t, client) }, + }, + // The testaccesslist is a bit time-sensitive: the newTestBackend imports + // one block. The `testAccessList` fails if the miner has not yet created a + // new pending-block after the import event. + // Hence: this test should be last, execute the tests serially. + { + "TestAccessList", + func(t *testing.T) { testAccessList(t, client) }, + }, + { + "TestTraceTransaction", + func(t *testing.T) { testTraceTransactions(t, client, txHashes) }, + }, + { + "TestSetHead", + func(t *testing.T) { testSetHead(t, client) }, + }, + } + for _, tt := range tests { + t.Run(tt.name, tt.test) + } +} + +func testAccessList(t *testing.T, client *rpc.Client) { + ec := New(client) + baseFee := new(big.Int).SetUint64(params.InitialBaseFee) + + for i, tc := range []struct { + msg ethereum.CallMsg + wantGas uint64 + wantErr string + wantVMErr string + wantAL string + }{ + { // Test transfer + msg: ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: baseFee, + Value: big.NewInt(1), + }, + wantGas: 21000, + wantAL: `[]`, + }, + { // Test reverting transaction + msg: ethereum.CallMsg{ + From: testAddr, + To: nil, + Gas: 100000, + GasPrice: baseFee, + Value: big.NewInt(1), + Data: common.FromHex("0x608060806080608155fd"), + }, + wantGas: 78018, + wantVMErr: "execution reverted", + wantAL: `[ + { + "address": "0xdb7d6ab1f17c6b31909ae466702703daef9269cf", + "storageKeys": [ + "0x0000000000000000000000000000000000000000000000000000000000000081" + ] + } +]`, + }, + { // error when gasPrice is less than baseFee + msg: ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: big.NewInt(1), // less than baseFee + Value: big.NewInt(1), + }, + wantErr: "max fee per gas less than block base fee", + }, + { // when gasPrice is not specified + msg: ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + Value: big.NewInt(1), + }, + wantGas: 21000, + wantAL: `[]`, + }, + } { + al, gas, vmErr, err := ec.CreateAccessList(context.Background(), tc.msg) + if tc.wantErr != "" { + if !strings.Contains(err.Error(), tc.wantErr) { + t.Fatalf("test %d: wrong error: %v", i, err) + } + continue + } else if err != nil { + t.Fatalf("test %d: wrong error: %v", i, err) + } + if have, want := vmErr, tc.wantVMErr; have != want { + t.Fatalf("test %d: vmErr wrong, have %v want %v", i, have, want) + } + if have, want := gas, tc.wantGas; have != want { + t.Fatalf("test %d: gas wrong, have %v want %v", i, have, want) + } + haveList, _ := json.MarshalIndent(al, "", " ") + if have, want := string(haveList), tc.wantAL; have != want { + t.Fatalf("test %d: access list wrong, have:\n%v\nwant:\n%v", i, have, want) + } + } +} + +func testGCStats(t *testing.T, client *rpc.Client) { + ec := New(client) + _, err := ec.GCStats(context.Background()) + if err != nil { + t.Fatal(err) + } +} + +func testMemStats(t *testing.T, client *rpc.Client) { + ec := New(client) + stats, err := ec.MemStats(context.Background()) + if err != nil { + t.Fatal(err) + } + if stats.Alloc == 0 { + t.Fatal("Invalid mem stats retrieved") + } +} + +func testGetNodeInfo(t *testing.T, client *rpc.Client) { + ec := New(client) + info, err := ec.GetNodeInfo(context.Background()) + if err != nil { + t.Fatal(err) + } + + if info.Name == "" { + t.Fatal("Invalid node info retrieved") + } +} + +func testSetHead(t *testing.T, client *rpc.Client) { + ec := New(client) + err := ec.SetHead(context.Background(), big.NewInt(0)) + if err != nil { + t.Fatal(err) + } +} + +func testSubscribePendingTransactions(t *testing.T, client *rpc.Client) { + ec := New(client) + ethcl := ethclient.NewClient(client) + baseFee := new(big.Int).SetUint64(params.InitialBaseFee) + + // Subscribe to Transactions + ch1 := make(chan common.Hash) + ec.SubscribePendingTransactions(context.Background(), ch1) + + // Subscribe to Transactions + ch2 := make(chan *types.Transaction) + ec.SubscribeFullPendingTransactions(context.Background(), ch2) + + // Send a transaction + chainID, err := ethcl.ChainID(context.Background()) + if err != nil { + t.Fatal(err) + } + nonce, err := ethcl.NonceAt(context.Background(), testAddr, nil) + if err != nil { + t.Fatal(err) + } + // Create transaction + tx := types.NewTransaction(nonce, common.Address{1}, big.NewInt(1), 22000, baseFee, nil) + signer := types.LatestSignerForChainID(chainID) + signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) + if err != nil { + t.Fatal(err) + } + signedTx, err := tx.WithSignature(signer, signature) + if err != nil { + t.Fatal(err) + } + // Send transaction + err = ethcl.SendTransaction(context.Background(), signedTx) + if err != nil { + t.Fatal(err) + } + // Check that the transaction was sent over the channel + hash := <-ch1 + if hash != signedTx.Hash() { + t.Fatalf("Invalid tx hash received, got %v, want %v", hash, signedTx.Hash()) + } + // Check that the transaction was sent over the channel + tx = <-ch2 + if tx.Hash() != signedTx.Hash() { + t.Fatalf("Invalid tx hash received, got %v, want %v", tx.Hash(), signedTx.Hash()) + } +} + +func testCallContract(t *testing.T, client *rpc.Client) { + ec := New(client) + baseFee := new(big.Int).SetUint64(params.InitialBaseFee) + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: baseFee, + Value: big.NewInt(1), + } + // CallContract without override + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } + // CallContract with override + override := OverrideAccount{ + Nonce: 1, + } + mapAcc := make(map[common.Address]OverrideAccount) + mapAcc[testAddr] = override + if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + +func testTraceTransactions(t *testing.T, client *rpc.Client, txHashes []common.Hash) { + ec := New(client) + for _, txHash := range txHashes { + // Struct logger + _, err := ec.TraceTransaction(context.Background(), txHash, nil) + if err != nil { + t.Fatal(err) + } + + // Struct logger + _, err = ec.TraceTransaction(context.Background(), txHash, + &tracers.TraceConfig{}, + ) + if err != nil { + t.Fatal(err) + } + } +} + func TestOverrideAccountMarshal(t *testing.T) { om := map[common.Address]OverrideAccount{ {0x11}: { @@ -109,3 +494,98 @@ func TestBlockOverridesMarshal(t *testing.T) { } } } + +func testCallContractWithBlockOverrides(t *testing.T, client *rpc.Client) { + ec := New(client) + baseFee := new(big.Int).SetUint64(params.InitialBaseFee) + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 50000, + GasPrice: baseFee, + Value: big.NewInt(1), + } + override := OverrideAccount{ + // Returns coinbase address. + Code: common.FromHex("0x41806000526014600cf3"), + } + mapAcc := make(map[common.Address]OverrideAccount) + mapAcc[common.Address{}] = override + res, err := ec.CallContract(context.Background(), msg, big.NewInt(0), &mapAcc) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(res, common.FromHex("0x0000000000000000000000000000000000000000")) { + t.Fatalf("unexpected result: %x", res) + } + + // Now test with block overrides + bo := BlockOverrides{ + Coinbase: common.HexToAddress("0x1111111111111111111111111111111111111111"), + } + res, err = ec.CallContractWithBlockOverrides(context.Background(), msg, big.NewInt(0), &mapAcc, bo) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(res, common.FromHex("0x1111111111111111111111111111111111111111")) { + t.Fatalf("unexpected result: %x", res) + } +} + +func testTraceTransactionWithCallTracer(t *testing.T, client *rpc.Client, txHashes []common.Hash) { + ec := New(client) + for _, txHash := range txHashes { + // With nil config (defaults). + result, err := ec.TraceTransactionWithCallTracer(context.Background(), txHash, nil) + if err != nil { + t.Fatalf("nil config: %v", err) + } + if result.Type != "CALL" { + t.Fatalf("unexpected type: %s", result.Type) + } + if result.From == (common.Address{}) { + t.Fatal("from is zero") + } + if result.Gas == 0 { + t.Fatal("gas is zero") + } + + // With explicit config. + result, err = ec.TraceTransactionWithCallTracer(context.Background(), txHash, + &CallTracerConfig{}, + ) + if err != nil { + t.Fatalf("explicit config: %v", err) + } + if result.Type != "CALL" { + t.Fatalf("unexpected type: %s", result.Type) + } + } +} + +func testTraceCallWithCallTracer(t *testing.T, client *rpc.Client) { + ec := New(client) + baseFee := new(big.Int).SetUint64(params.InitialBaseFee) + msg := ethereum.CallMsg{ + From: testAddr, + To: &common.Address{}, + Gas: 21000, + GasPrice: baseFee, + Value: big.NewInt(1), + } + result, err := ec.TraceCallWithCallTracer(context.Background(), msg, + rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber), nil, nil, nil, + ) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if result.Type != "CALL" { + t.Fatalf("unexpected type: %s", result.Type) + } + if result.From == (common.Address{}) { + t.Fatal("from is zero") + } + if result.Gas == 0 { + t.Fatal("gas is zero") + } +}