diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go
new file mode 100644
index 0000000000..948848ae72
--- /dev/null
+++ b/ethclient/ethclient_test.go
@@ -0,0 +1,1089 @@
+// Copyright 2016 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package ethclient_test
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "math/big"
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ ethereum "github.com/XinFinOrg/XDPoSChain"
+ "github.com/XinFinOrg/XDPoSChain/XDCx"
+ "github.com/XinFinOrg/XDPoSChain/XDCxlending"
+ "github.com/XinFinOrg/XDPoSChain/accounts/abi"
+ "github.com/XinFinOrg/XDPoSChain/common"
+ "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
+ "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
+ "github.com/XinFinOrg/XDPoSChain/core"
+ "github.com/XinFinOrg/XDPoSChain/core/rawdb"
+ "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/ethclient"
+ "github.com/XinFinOrg/XDPoSChain/log"
+ "github.com/XinFinOrg/XDPoSChain/node"
+ "github.com/XinFinOrg/XDPoSChain/params"
+ "github.com/XinFinOrg/XDPoSChain/rpc"
+)
+
+// Verify that Client implements the ethereum interfaces.
+var (
+ _ = ethereum.ChainReader(ðclient.Client{})
+ _ = ethereum.TransactionReader(ðclient.Client{})
+ _ = ethereum.ChainStateReader(ðclient.Client{})
+ _ = ethereum.ChainSyncReader(ðclient.Client{})
+ _ = ethereum.ContractCaller(ðclient.Client{})
+ _ = ethereum.GasEstimator(ðclient.Client{})
+ _ = ethereum.GasPricer(ðclient.Client{})
+ _ = ethereum.LogFilterer(ðclient.Client{})
+ _ = ethereum.PendingStateReader(ðclient.Client{})
+ // _ = ethereum.PendingStateEventer(ðclient.Client{})
+ _ = ethereum.PendingContractCaller(ðclient.Client{})
+)
+
+var (
+ testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+ testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+ testBalance = big.NewInt(2e15)
+ revertContractAddr = common.HexToAddress("290f1b36649a61e369c6276f6d29463335b4400c")
+ revertCode = common.FromHex("7f08c379a0000000000000000000000000000000000000000000000000000000006000526020600452600a6024527f75736572206572726f7200000000000000000000000000000000000000000000604452604e6000fd")
+)
+
+var testChainConfig = func() *params.ChainConfig {
+ config := *params.AllXDPoSProtocolChanges
+ if config.XDPoS != nil {
+ xdpos := *config.XDPoS
+ if xdpos.Period == 0 {
+ xdpos.Period = 2
+ }
+ if xdpos.RewardCheckpoint == 0 {
+ xdpos.RewardCheckpoint = 900
+ }
+ config.XDPoS = &xdpos
+ }
+ if config.Eip1559Block == nil {
+ config.Eip1559Block = big.NewInt(0)
+ }
+ // Ensure global chain constants match the test chain config.
+ common.CopyConstants(config.ChainID.Uint64())
+ return &config
+}()
+
+var genesis = &core.Genesis{
+ Config: testChainConfig,
+ Alloc: types.GenesisAlloc{
+ testAddr: {Balance: testBalance},
+ revertContractAddr: {Code: revertCode},
+ },
+ ExtraData: append(make([]byte, 32), make([]byte, crypto.SignatureLength)...),
+ Timestamp: 9000,
+ BaseFee: big.NewInt(params.InitialBaseFee),
+}
+
+var testTx1 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
+ Nonce: 0,
+ Value: big.NewInt(12),
+ GasPrice: big.NewInt(params.InitialBaseFee),
+ Gas: params.TxGas,
+ To: &common.Address{2},
+})
+
+var testTx2 = types.MustSignNewTx(testKey, types.LatestSigner(genesis.Config), &types.LegacyTx{
+ Nonce: 1,
+ Value: big.NewInt(8),
+ GasPrice: big.NewInt(params.InitialBaseFee),
+ Gas: params.TxGas,
+ To: &common.Address{2},
+})
+
+var xdposTestSignature = func() []byte {
+ sig, err := crypto.Sign(common.Hash{}.Bytes(), testKey)
+ if err != nil {
+ panic(fmt.Errorf("failed to sign XDPoS test header: %w", err))
+ }
+ return sig
+}()
+
+func newTestBackend(config *node.Config) (*node.Node, []*types.Block, error) {
+ // Generate test chain.
+ blocks := generateTestChain()
+
+ // Create node
+ if config == nil {
+ config = new(node.Config)
+ }
+ if config.DataDir == "" {
+ dataDir, err := os.MkdirTemp("", "xdpos-ethclient-")
+ if err != nil {
+ return nil, nil, fmt.Errorf("can't create temp datadir: %v", err)
+ }
+ config.DataDir = dataDir
+ }
+ n, err := node.New(config)
+ if err != nil {
+ return nil, nil, fmt.Errorf("can't create new node: %v", err)
+ }
+ // Create XDCx and lending services required by eth.New
+ xdcx := XDCx.New(n, &XDCx.Config{DataDir: filepath.Join(config.DataDir, "XDCx")})
+ lending := XDCxlending.New(n, xdcx)
+ // Create Ethereum Service
+ ecfg := ðconfig.Config{Genesis: genesis, RPCGasCap: 1000000}
+ ethservice, err := eth.New(n, ecfg, xdcx, lending)
+ if err != nil {
+ return nil, nil, fmt.Errorf("can't create new ethereum service: %v", err)
+ }
+
+ // Import the test chain.
+ prevLog := log.Root()
+ log.SetDefault(log.NewLogger(log.DiscardHandler()))
+ if err := n.Start(); err != nil {
+ log.SetDefault(prevLog)
+ return nil, nil, fmt.Errorf("can't start test node: %v", err)
+ }
+ log.SetDefault(prevLog)
+ if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
+ return nil, nil, fmt.Errorf("can't import test blocks: %v", err)
+ }
+ return n, blocks, nil
+}
+
+func generateTestChain() []*types.Block {
+ generate := func(i int, g *core.BlockGen) {
+ g.OffsetTime(5)
+ extra := make([]byte, utils.ExtraVanity+utils.ExtraSeal)
+ copy(extra[len(extra)-utils.ExtraSeal:], xdposTestSignature)
+ g.SetExtra(extra)
+ if i == 1 {
+ // Test transactions are included in block #2.
+ g.AddTx(testTx1)
+ g.AddTx(testTx2)
+ }
+ }
+ consensus := XDPoS.NewFaker(rawdb.NewMemoryDatabase(), genesis.Config)
+ _, blocks, _ := core.GenerateChainWithGenesis(genesis, consensus, 2, generate)
+ return append([]*types.Block{genesis.ToBlock()}, blocks...)
+}
+
+func TestEthClient(t *testing.T) {
+ prevLog := log.Root()
+ log.SetDefault(log.NewLogger(log.DiscardHandler()))
+ defer log.SetDefault(prevLog)
+
+ backend, chain, err := newTestBackend(nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ client := backend.Attach()
+ defer backend.Close()
+ defer client.Close()
+
+ tests := map[string]struct {
+ test func(t *testing.T)
+ }{
+ "Header": {
+ func(t *testing.T) { testHeader(t, chain, client) },
+ },
+ "BalanceAt": {
+ func(t *testing.T) { testBalanceAt(t, client) },
+ },
+ "TxInBlockInterrupted": {
+ func(t *testing.T) { testTransactionInBlock(t, client) },
+ },
+ "ChainID": {
+ func(t *testing.T) { testChainID(t, client) },
+ },
+ "GetBlock": {
+ func(t *testing.T) { testGetBlock(t, client) },
+ },
+ "StatusFunctions": {
+ func(t *testing.T) { testStatusFunctions(t, client) },
+ },
+ "CallContract": {
+ func(t *testing.T) { testCallContract(t, client) },
+ },
+ "CallContractAtHash": {
+ func(t *testing.T) { testCallContractAtHash(t, client) },
+ },
+ "AtFunctions": {
+ func(t *testing.T) { testAtFunctions(t, client) },
+ },
+ "TransactionSender": {
+ func(t *testing.T) { testTransactionSender(t, client) },
+ },
+ }
+
+ t.Parallel()
+ for name, tt := range tests {
+ t.Run(name, tt.test)
+ }
+}
+
+func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) {
+ tests := map[string]struct {
+ block *big.Int
+ want *types.Header
+ wantErr error
+ }{
+ "genesis": {
+ block: big.NewInt(0),
+ want: chain[0].Header(),
+ },
+ "first_block": {
+ block: big.NewInt(1),
+ want: chain[1].Header(),
+ },
+ "future_block": {
+ block: big.NewInt(1000000000),
+ want: nil,
+ wantErr: ethereum.ErrNotFound,
+ },
+ }
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ ec := ethclient.NewClient(client)
+ ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+ defer cancel()
+
+ got, err := ec.HeaderByNumber(ctx, tt.block)
+ if !errors.Is(err, tt.wantErr) {
+ t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr)
+ }
+ if got != nil && got.Number != nil && got.Number.Sign() == 0 {
+ got.Number = big.NewInt(0) // hack to make DeepEqual work
+ }
+ if tt.want == nil {
+ if got != nil {
+ t.Fatalf("HeaderByNumber(%v) got = %v, want nil", tt.block, got)
+ }
+ return
+ }
+ if got == nil {
+ t.Fatalf("HeaderByNumber(%v) got = nil, want %v", tt.block, tt.want)
+ }
+ if got.Hash() != tt.want.Hash() {
+ t.Fatalf("HeaderByNumber(%v) got = %v, want %v", tt.block, got, tt.want)
+ }
+ })
+ }
+}
+
+func testBalanceAt(t *testing.T, client *rpc.Client) {
+ prevLog := log.Root()
+ log.SetDefault(log.NewLogger(log.DiscardHandler()))
+ defer log.SetDefault(prevLog)
+
+ tests := map[string]struct {
+ account common.Address
+ block *big.Int
+ want *big.Int
+ wantErr error
+ }{
+ "valid_account_genesis": {
+ account: testAddr,
+ block: big.NewInt(0),
+ want: testBalance,
+ },
+ "valid_account": {
+ account: testAddr,
+ block: big.NewInt(1),
+ want: testBalance,
+ },
+ "non_existent_account": {
+ account: common.Address{1},
+ block: big.NewInt(1),
+ want: big.NewInt(0),
+ },
+ "future_block": {
+ account: testAddr,
+ block: big.NewInt(1000000000),
+ want: big.NewInt(0),
+ wantErr: errors.New("header for number not found"),
+ },
+ }
+ for name, tt := range tests {
+ t.Run(name, func(t *testing.T) {
+ ec := ethclient.NewClient(client)
+ ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
+ defer cancel()
+
+ got, err := ec.BalanceAt(ctx, tt.account, tt.block)
+ if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) {
+ t.Fatalf("BalanceAt(%x, %v) error = %q, want %q", tt.account, tt.block, err, tt.wantErr)
+ }
+ if got.Cmp(tt.want) != 0 {
+ t.Fatalf("BalanceAt(%x, %v) = %v, want %v", tt.account, tt.block, got, tt.want)
+ }
+ })
+ }
+}
+
+func testTransactionInBlock(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+
+ // Get current block by number.
+ block, err := ec.BlockByNumber(context.Background(), nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ // Test tx in block not found.
+ if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 20); err != ethereum.ErrNotFound {
+ t.Fatal("error should be ethereum.ErrNotFound")
+ }
+
+ // Test tx in block found.
+ tx, err := ec.TransactionInBlock(context.Background(), block.Hash(), 0)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if tx.Hash() != testTx1.Hash() {
+ t.Fatalf("unexpected transaction: %v", tx)
+ }
+
+ tx, err = ec.TransactionInBlock(context.Background(), block.Hash(), 1)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if tx.Hash() != testTx2.Hash() {
+ t.Fatalf("unexpected transaction: %v", tx)
+ }
+}
+
+func testChainID(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+ id, err := ec.ChainID(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if id == nil || id.Cmp(genesis.Config.ChainID) != 0 {
+ t.Fatalf("ChainID returned wrong number: %+v", id)
+ }
+}
+
+func testGetBlock(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+
+ // Get current block number
+ blockNumber, err := ec.BlockNumber(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if blockNumber != 2 {
+ t.Fatalf("BlockNumber returned wrong number: %d", blockNumber)
+ }
+ // Get current block by number
+ block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if block.NumberU64() != blockNumber {
+ t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64())
+ }
+ // Get current block by hash
+ blockH, err := ec.BlockByHash(context.Background(), block.Hash())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if block.Hash() != blockH.Hash() {
+ t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex())
+ }
+ // Get header by number
+ header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if block.Header().Hash() != header.Hash() {
+ t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex())
+ }
+ // Get header by hash
+ headerH, err := ec.HeaderByHash(context.Background(), block.Hash())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if block.Header().Hash() != headerH.Hash() {
+ t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex())
+ }
+}
+
+func testStatusFunctions(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+
+ // Sync progress
+ progress, err := ec.SyncProgress(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if progress != nil {
+ t.Fatalf("unexpected progress: %v", progress)
+ }
+
+ // NetworkID
+ networkID, err := ec.NetworkID(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if networkID.Cmp(genesis.Config.ChainID) != 0 {
+ t.Fatalf("unexpected networkID: %v", networkID)
+ }
+
+ // SuggestGasPrice
+ gasPrice, err := ec.SuggestGasPrice(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if gasPrice.Cmp(big.NewInt(params.InitialBaseFee)) != 0 {
+ t.Fatalf("unexpected gas price: %v", gasPrice)
+ }
+
+ // SuggestGasTipCap
+ gasTipCap, err := ec.SuggestGasTipCap(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if gasTipCap.Sign() < 0 {
+ t.Fatalf("unexpected gas tip cap: %v", gasTipCap)
+ }
+
+ // BlobBaseFee
+ blobBaseFee, err := ec.BlobBaseFee(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if blobBaseFee.Sign() < 0 {
+ t.Fatalf("unexpected blob base fee: %v", blobBaseFee)
+ }
+
+ // FeeHistory
+ history, err := ec.FeeHistory(context.Background(), 1, big.NewInt(2), []float64{95, 99})
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if history.OldestBlock == nil || history.OldestBlock.Cmp(big.NewInt(2)) != 0 {
+ t.Fatalf("unexpected oldest block: %v", history.OldestBlock)
+ }
+ if len(history.Reward) != 1 || len(history.Reward[0]) != 2 {
+ t.Fatalf("unexpected reward history: %v", history.Reward)
+ }
+ if len(history.BaseFee) != 2 || history.BaseFee[0].Sign() <= 0 || history.BaseFee[1].Sign() <= 0 {
+ t.Fatalf("unexpected base fee history: %v", history.BaseFee)
+ }
+ if len(history.GasUsedRatio) != 1 {
+ t.Fatalf("unexpected gas used ratio: %v", history.GasUsedRatio)
+ }
+}
+
+func testCallContractAtHash(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+
+ // EstimateGas
+ msg := ethereum.CallMsg{
+ From: testAddr,
+ To: &common.Address{},
+ Gas: 21000,
+ Value: big.NewInt(1),
+ }
+ gas, err := ec.EstimateGas(context.Background(), msg)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if gas != 21000 {
+ t.Fatalf("unexpected gas price: %v", gas)
+ }
+ block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1))
+ if err != nil {
+ t.Fatalf("BlockByNumber error: %v", err)
+ }
+ // CallContract
+ if _, err := ec.CallContractAtHash(context.Background(), msg, block.Hash()); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func testCallContract(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+
+ // EstimateGas
+ msg := ethereum.CallMsg{
+ From: testAddr,
+ To: &common.Address{},
+ Gas: 21000,
+ Value: big.NewInt(1),
+ }
+ gas, err := ec.EstimateGas(context.Background(), msg)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if gas != 21000 {
+ t.Fatalf("unexpected gas price: %v", gas)
+ }
+ // CallContract
+ if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ // PendingCallContract
+ if _, err := ec.PendingCallContract(context.Background(), msg); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+}
+
+func testAtFunctions(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+
+ block, err := ec.HeaderByNumber(context.Background(), big.NewInt(1))
+ if err != nil {
+ t.Fatalf("BlockByNumber error: %v", err)
+ }
+
+ baseNonce, err := ec.NonceAt(context.Background(), testAddr, nil)
+ if err != nil {
+ t.Fatalf("NonceAt error: %v", err)
+ }
+
+ // send a transaction for some interesting pending status
+ if err := sendTransaction(ec); err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+
+ // wait for the transaction to be included in the pending block
+ deadline := time.Now().Add(20 * time.Second)
+ for {
+ pending, err := ec.PendingTransactionCount(context.Background())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ pendingNonce, err := ec.PendingNonceAt(context.Background(), testAddr)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if pending > 0 || pendingNonce == baseNonce+1 {
+ break
+ }
+ if time.Now().After(deadline) {
+ t.Fatalf("pending transaction not observed")
+ }
+ time.Sleep(100 * time.Millisecond)
+ }
+
+ // Query balance
+ balance, err := ec.BalanceAt(context.Background(), testAddr, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ hashBalance, err := ec.BalanceAtHash(context.Background(), testAddr, block.Hash())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if balance.Cmp(hashBalance) == 0 {
+ t.Fatalf("unexpected balance at hash: %v %v", balance, hashBalance)
+ }
+ penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if penBalance.Cmp(balance) > 0 {
+ t.Fatalf("unexpected pending balance: %v %v", balance, penBalance)
+ }
+ // NonceAt
+ nonce, err := ec.NonceAt(context.Background(), testAddr, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ hashNonce, err := ec.NonceAtHash(context.Background(), testAddr, block.Hash())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if hashNonce == nonce {
+ t.Fatalf("unexpected nonce at hash: %v %v", nonce, hashNonce)
+ }
+ penNonce, err := ec.PendingNonceAt(context.Background(), testAddr)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if penNonce != nonce+1 {
+ t.Fatalf("unexpected nonce: %v %v", nonce, penNonce)
+ }
+ // StorageAt
+ storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ hashStorage, err := ec.StorageAtHash(context.Background(), testAddr, common.Hash{}, block.Hash())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !bytes.Equal(storage, hashStorage) {
+ t.Fatalf("unexpected storage at hash: %v %v", storage, hashStorage)
+ }
+ penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{})
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !bytes.Equal(storage, penStorage) {
+ t.Fatalf("unexpected storage: %v %v", storage, penStorage)
+ }
+ // CodeAt
+ code, err := ec.CodeAt(context.Background(), testAddr, nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ hashCode, err := ec.CodeAtHash(context.Background(), common.Address{}, block.Hash())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !bytes.Equal(code, hashCode) {
+ t.Fatalf("unexpected code at hash: %v %v", code, hashCode)
+ }
+ penCode, err := ec.PendingCodeAt(context.Background(), testAddr)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !bytes.Equal(code, penCode) {
+ t.Fatalf("unexpected code: %v %v", code, penCode)
+ }
+ // Use HeaderByNumber to get a header for EstimateGasAtBlock and EstimateGasAtBlockHash
+ latestHeader, err := ec.HeaderByNumber(context.Background(), nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ // EstimateGasAtBlock
+ msg := ethereum.CallMsg{
+ From: testAddr,
+ To: &common.Address{},
+ Gas: 21000,
+ Value: big.NewInt(1),
+ }
+ gas, err := ec.EstimateGasAtBlock(context.Background(), msg, latestHeader.Number)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if gas != 21000 {
+ t.Fatalf("unexpected gas limit: %v", gas)
+ }
+ // EstimateGasAtBlockHash
+ gas, err = ec.EstimateGasAtBlockHash(context.Background(), msg, latestHeader.Hash())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if gas != 21000 {
+ t.Fatalf("unexpected gas limit: %v", gas)
+ }
+
+ // Verify that sender address of transaction from latest block is saved in cache.
+ latestBlock, err := ec.BlockByNumber(context.Background(), nil)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if len(latestBlock.Transactions()) == 0 {
+ t.Fatal("latest block does not contain transactions")
+ }
+ // No additional RPC should be required, ensure the server is not asked by
+ // canceling the context.
+ sender, err := ec.TransactionSender(newCanceledContext(), latestBlock.Transactions()[0], latestBlock.Hash(), 0)
+ if err != nil {
+ t.Fatal("unable to recover sender:", err)
+ }
+ if sender != testAddr {
+ t.Fatal("wrong sender:", sender)
+ }
+}
+
+func testTransactionSender(t *testing.T, client *rpc.Client) {
+ ec := ethclient.NewClient(client)
+ ctx := context.Background()
+
+ // Retrieve testTx1 via RPC.
+ block2, err := ec.HeaderByNumber(ctx, big.NewInt(2))
+ if err != nil {
+ t.Fatal("can't get block 1:", err)
+ }
+ tx1, err := ec.TransactionInBlock(ctx, block2.Hash(), 0)
+ if err != nil {
+ t.Fatal("can't get tx:", err)
+ }
+ if tx1.Hash() != testTx1.Hash() {
+ t.Fatalf("wrong tx hash %v, want %v", tx1.Hash(), testTx1.Hash())
+ }
+
+ // The sender address is cached in tx1, so no additional RPC should be required in
+ // TransactionSender. Ensure the server is not asked by canceling the context here.
+ sender1, err := ec.TransactionSender(newCanceledContext(), tx1, block2.Hash(), 0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if sender1 != testAddr {
+ t.Fatal("wrong sender:", sender1)
+ }
+
+ // Now try to get the sender of testTx2, which was not fetched through RPC.
+ // TransactionSender should query the server here.
+ sender2, err := ec.TransactionSender(ctx, testTx2, block2.Hash(), 1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if sender2 != testAddr {
+ t.Fatal("wrong sender:", sender2)
+ }
+}
+
+func TestBlockReceiptsPreservesCanonicalFlag(t *testing.T) {
+ srv := rpc.NewServer()
+ service := &blockReceiptsTestService{calls: make(chan rpc.BlockNumberOrHash, 1)}
+ if err := srv.RegisterName("eth", service); err != nil {
+ t.Fatalf("failed to register service: %v", err)
+ }
+ defer srv.Stop()
+
+ client := rpc.DialInProc(srv)
+ defer client.Close()
+
+ ec := ethclient.NewClient(client)
+ defer ec.Close()
+
+ hash := common.HexToHash("0x01")
+ ref := rpc.BlockNumberOrHashWithHash(hash, true)
+
+ if _, err := ec.BlockReceipts(context.Background(), ref); err != nil {
+ t.Fatalf("BlockReceipts returned error: %v", err)
+ }
+
+ select {
+ case call := <-service.calls:
+ if call.BlockHash == nil || *call.BlockHash != hash {
+ t.Fatalf("unexpected block hash: got %v, want %v", call.BlockHash, hash)
+ }
+ if !call.RequireCanonical {
+ t.Fatalf("requireCanonical flag was lost: %+v", call)
+ }
+ default:
+ t.Fatal("service was not called")
+ }
+}
+
+type blockReceiptsTestService struct {
+ calls chan rpc.BlockNumberOrHash
+}
+
+func (s *blockReceiptsTestService) GetBlockReceipts(ctx context.Context, block rpc.BlockNumberOrHash) ([]*types.Receipt, error) {
+ s.calls <- block
+ return []*types.Receipt{}, nil
+}
+
+func newCanceledContext() context.Context {
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+ <-ctx.Done() // Ensure the close of the Done channel
+ return ctx
+}
+
+func sendTransaction(ec *ethclient.Client) error {
+ chainID, err := ec.ChainID(context.Background())
+ if err != nil {
+ return err
+ }
+ nonce, err := ec.NonceAt(context.Background(), testAddr, nil)
+ if err != nil {
+ return err
+ }
+
+ signer := types.LatestSignerForChainID(chainID)
+ tx, err := types.SignNewTx(testKey, signer, &types.LegacyTx{
+ Nonce: nonce,
+ To: &common.Address{2},
+ Value: big.NewInt(0),
+ Gas: params.TxGas,
+ GasPrice: big.NewInt(params.InitialBaseFee),
+ })
+ if err != nil {
+ return err
+ }
+ return ec.SendTransaction(context.Background(), tx)
+}
+
+// Here we show how to get the error message of reverted contract call.
+func ExampleRevertErrorData() {
+ // First create an ethclient.Client instance.
+ ctx := context.Background()
+ ec, _ := ethclient.DialContext(ctx, exampleNode.HTTPEndpoint())
+
+ // Call the contract.
+ // Note we expect the call to return an error.
+ contract := common.HexToAddress("290f1b36649a61e369c6276f6d29463335b4400c")
+ call := ethereum.CallMsg{To: &contract, Gas: 30000}
+ result, err := ec.CallContract(ctx, call, nil)
+ if len(result) > 0 {
+ panic("got result")
+ }
+ if err == nil {
+ panic("call did not return error")
+ }
+
+ // Extract the low-level revert data from the error.
+ revertData, ok := ethclient.RevertErrorData(err)
+ if !ok {
+ panic("unpacking revert failed")
+ }
+ fmt.Printf("revert: %x\n", revertData)
+
+ // Parse the revert data to obtain the error message.
+ message, err := abi.UnpackRevert(revertData)
+ if err != nil {
+ panic("parsing ABI error failed: " + err.Error())
+ }
+ fmt.Println("message:", message)
+
+ // Output:
+ // revert: 08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a75736572206572726f72
+ // message: user error
+}
+
+func TestSimulateV1(t *testing.T) {
+ backend, _, err := newTestBackend(nil)
+ if err != nil {
+ t.Fatalf("Failed to create test backend: %v", err)
+ }
+ defer backend.Close()
+
+ client := ethclient.NewClient(backend.Attach())
+ defer client.Close()
+
+ ctx := context.Background()
+
+ baseFee := big.NewInt(params.InitialBaseFee)
+
+ // Simple test: transfer ETH from one account to another
+ from := testAddr
+ to := common.HexToAddress("0x0000000000000000000000000000000000000001")
+ value := big.NewInt(100)
+ gas := uint64(100000)
+ maxFeePerGas := new(big.Int).Set(baseFee)
+
+ opts := ethclient.SimulateOptions{
+ BlockStateCalls: []ethclient.SimulateBlock{
+ {
+ Calls: []ethereum.CallMsg{
+ {
+ From: from,
+ To: &to,
+ Value: value,
+ Gas: gas,
+ GasFeeCap: maxFeePerGas,
+ },
+ },
+ },
+ },
+ Validation: true,
+ }
+
+ results, err := client.SimulateV1(ctx, opts, nil)
+ if err != nil {
+ t.Fatalf("SimulateV1 failed: %v", err)
+ }
+
+ if len(results) != 1 {
+ t.Fatalf("expected 1 block result, got %d", len(results))
+ }
+
+ if len(results[0].Calls) != 1 {
+ t.Fatalf("expected 1 call result, got %d", len(results[0].Calls))
+ }
+
+ // Check that the transaction succeeded
+ if results[0].Calls[0].Status != 1 {
+ t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status)
+ }
+
+ if results[0].Calls[0].Error != nil {
+ t.Errorf("expected no error, got %v", results[0].Calls[0].Error)
+ }
+}
+
+func TestSimulateV1WithBlockOverrides(t *testing.T) {
+ backend, _, err := newTestBackend(nil)
+ if err != nil {
+ t.Fatalf("Failed to create test backend: %v", err)
+ }
+ defer backend.Close()
+
+ client := ethclient.NewClient(backend.Attach())
+ defer client.Close()
+
+ ctx := context.Background()
+
+ baseFee := big.NewInt(params.InitialBaseFee)
+
+ from := testAddr
+ to := common.HexToAddress("0x0000000000000000000000000000000000000001")
+ value := big.NewInt(100)
+ gas := uint64(100000)
+ maxFeePerGas := new(big.Int).Set(baseFee)
+
+ // Override timestamp only
+ timestamp := uint64(1234567890)
+
+ opts := ethclient.SimulateOptions{
+ BlockStateCalls: []ethclient.SimulateBlock{
+ {
+ BlockOverrides: ðereum.BlockOverrides{
+ Time: timestamp,
+ },
+ Calls: []ethereum.CallMsg{
+ {
+ From: from,
+ To: &to,
+ Value: value,
+ Gas: gas,
+ GasFeeCap: maxFeePerGas,
+ },
+ },
+ },
+ },
+ Validation: true,
+ }
+
+ results, err := client.SimulateV1(ctx, opts, nil)
+ if err != nil {
+ t.Fatalf("SimulateV1 with block overrides failed: %v", err)
+ }
+
+ if len(results) != 1 {
+ t.Fatalf("expected 1 block result, got %d", len(results))
+ }
+
+ // Verify the timestamp was overridden
+ if results[0].Timestamp != timestamp {
+ t.Errorf("expected timestamp %d, got %d", timestamp, results[0].Timestamp)
+ }
+}
+
+func TestSimulateV1WithStateOverrides(t *testing.T) {
+ backend, _, err := newTestBackend(nil)
+ if err != nil {
+ t.Fatalf("Failed to create test backend: %v", err)
+ }
+ defer backend.Close()
+
+ client := ethclient.NewClient(backend.Attach())
+ defer client.Close()
+
+ ctx := context.Background()
+
+ baseFee := big.NewInt(params.InitialBaseFee)
+
+ from := testAddr
+ to := common.HexToAddress("0x0000000000000000000000000000000000000001")
+ value := big.NewInt(1000000000000000000) // 1 ETH
+ gas := uint64(100000)
+ maxFeePerGas := new(big.Int).Set(baseFee)
+
+ // Override the balance of the 'from' address
+ balanceStr := "1000000000000000000000"
+ balance := new(big.Int)
+ balance.SetString(balanceStr, 10)
+
+ stateOverrides := map[common.Address]ethereum.OverrideAccount{
+ from: {
+ Balance: balance,
+ },
+ }
+
+ opts := ethclient.SimulateOptions{
+ BlockStateCalls: []ethclient.SimulateBlock{
+ {
+ StateOverrides: stateOverrides,
+ Calls: []ethereum.CallMsg{
+ {
+ From: from,
+ To: &to,
+ Value: value,
+ Gas: gas,
+ GasFeeCap: maxFeePerGas,
+ },
+ },
+ },
+ },
+ Validation: true,
+ }
+
+ results, err := client.SimulateV1(ctx, opts, nil)
+ if err != nil {
+ t.Fatalf("SimulateV1 with state overrides failed: %v", err)
+ }
+
+ if len(results) != 1 {
+ t.Fatalf("expected 1 block result, got %d", len(results))
+ }
+
+ if results[0].Calls[0].Status != 1 {
+ t.Errorf("expected status 1 (success), got %d", results[0].Calls[0].Status)
+ }
+}
+
+func TestSimulateV1WithBlockNumberOrHash(t *testing.T) {
+ backend, _, err := newTestBackend(nil)
+ if err != nil {
+ t.Fatalf("Failed to create test backend: %v", err)
+ }
+ defer backend.Close()
+
+ client := ethclient.NewClient(backend.Attach())
+ defer client.Close()
+
+ ctx := context.Background()
+
+ baseFee := big.NewInt(params.InitialBaseFee)
+
+ from := testAddr
+ to := common.HexToAddress("0x0000000000000000000000000000000000000001")
+ value := big.NewInt(100)
+ gas := uint64(100000)
+ maxFeePerGas := new(big.Int).Set(baseFee)
+
+ opts := ethclient.SimulateOptions{
+ BlockStateCalls: []ethclient.SimulateBlock{
+ {
+ Calls: []ethereum.CallMsg{
+ {
+ From: from,
+ To: &to,
+ Value: value,
+ Gas: gas,
+ GasFeeCap: maxFeePerGas,
+ },
+ },
+ },
+ },
+ Validation: true,
+ }
+
+ // Simulate on the latest block
+ latest := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
+ results, err := client.SimulateV1(ctx, opts, &latest)
+ if err != nil {
+ t.Fatalf("SimulateV1 with latest block failed: %v", err)
+ }
+
+ if len(results) != 1 {
+ t.Fatalf("expected 1 block result, got %d", len(results))
+ }
+}
diff --git a/ethclient/example_test.go b/ethclient/example_test.go
new file mode 100644
index 0000000000..884aa6a4c6
--- /dev/null
+++ b/ethclient/example_test.go
@@ -0,0 +1,35 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package ethclient_test
+
+import (
+ "github.com/XinFinOrg/XDPoSChain/node"
+)
+
+var exampleNode *node.Node
+
+// launch example server
+func init() {
+ config := &node.Config{
+ HTTPHost: "127.0.0.1",
+ }
+ n, _, err := newTestBackend(config)
+ if err != nil {
+ panic("can't launch node: " + err.Error())
+ }
+ exampleNode = n
+}
diff --git a/params/config.go b/params/config.go
index 0ff0b1a346..16dff2bf15 100644
--- a/params/config.go
+++ b/params/config.go
@@ -346,7 +346,7 @@ var (
// This configuration is intentionally not using keyed fields to force anyone
// adding flags to the config to also have to set these fields.
AllXDPoSProtocolChanges = &ChainConfig{
- ChainID: big.NewInt(89),
+ ChainID: big.NewInt(1337),
HomesteadBlock: big.NewInt(0),
DAOForkBlock: nil,
DAOForkSupport: false,
@@ -366,7 +366,19 @@ var (
OsakaBlock: big.NewInt(0),
Ethash: nil,
Clique: nil,
- XDPoS: &XDPoSConfig{Period: 0, Epoch: 900},
+ XDPoS: &XDPoSConfig{
+ Epoch: 900,
+ Gap: 450,
+ SkipV1Validation: true,
+ FoundationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068"),
+ Reward: 250,
+ V2: &V2{
+ SwitchEpoch: 1,
+ SwitchBlock: big.NewInt(900),
+ CurrentConfig: UnitTestV2Configs[0],
+ AllConfigs: UnitTestV2Configs,
+ },
+ },
}
// AllCliqueProtocolChanges contains every protocol change (EIPs) introduced