github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/ethclient/ethclient_test.go (about)

     1  // Copyright 2021 The adkgo Authors
     2  // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8).
     3  //
     4  // the adkgo library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // the adkgo library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the adkgo library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package ethclient
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"math/big"
    25  	"reflect"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/aidoskuneen/adk-node"
    30  	"github.com/aidoskuneen/adk-node/common"
    31  	"github.com/aidoskuneen/adk-node/consensus/ethash"
    32  	"github.com/aidoskuneen/adk-node/core"
    33  	"github.com/aidoskuneen/adk-node/core/rawdb"
    34  	"github.com/aidoskuneen/adk-node/core/types"
    35  	"github.com/aidoskuneen/adk-node/crypto"
    36  	"github.com/aidoskuneen/adk-node/eth"
    37  	"github.com/aidoskuneen/adk-node/eth/ethconfig"
    38  	"github.com/aidoskuneen/adk-node/node"
    39  	"github.com/aidoskuneen/adk-node/params"
    40  	"github.com/aidoskuneen/adk-node/rpc"
    41  )
    42  
    43  // Verify that Client implements the ethereum interfaces.
    44  var (
    45  	_ = ethereum.ChainReader(&Client{})
    46  	_ = ethereum.TransactionReader(&Client{})
    47  	_ = ethereum.ChainStateReader(&Client{})
    48  	_ = ethereum.ChainSyncReader(&Client{})
    49  	_ = ethereum.ContractCaller(&Client{})
    50  	_ = ethereum.GasEstimator(&Client{})
    51  	_ = ethereum.GasPricer(&Client{})
    52  	_ = ethereum.LogFilterer(&Client{})
    53  	_ = ethereum.PendingStateReader(&Client{})
    54  	// _ = ethereum.PendingStateEventer(&Client{})
    55  	_ = ethereum.PendingContractCaller(&Client{})
    56  )
    57  
    58  func TestToFilterArg(t *testing.T) {
    59  	blockHashErr := fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock")
    60  	addresses := []common.Address{
    61  		common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"),
    62  	}
    63  	blockHash := common.HexToHash(
    64  		"0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb",
    65  	)
    66  
    67  	for _, testCase := range []struct {
    68  		name   string
    69  		input  ethereum.FilterQuery
    70  		output interface{}
    71  		err    error
    72  	}{
    73  		{
    74  			"without BlockHash",
    75  			ethereum.FilterQuery{
    76  				Addresses: addresses,
    77  				FromBlock: big.NewInt(1),
    78  				ToBlock:   big.NewInt(2),
    79  				Topics:    [][]common.Hash{},
    80  			},
    81  			map[string]interface{}{
    82  				"address":   addresses,
    83  				"fromBlock": "0x1",
    84  				"toBlock":   "0x2",
    85  				"topics":    [][]common.Hash{},
    86  			},
    87  			nil,
    88  		},
    89  		{
    90  			"with nil fromBlock and nil toBlock",
    91  			ethereum.FilterQuery{
    92  				Addresses: addresses,
    93  				Topics:    [][]common.Hash{},
    94  			},
    95  			map[string]interface{}{
    96  				"address":   addresses,
    97  				"fromBlock": "0x0",
    98  				"toBlock":   "latest",
    99  				"topics":    [][]common.Hash{},
   100  			},
   101  			nil,
   102  		},
   103  		{
   104  			"with negative fromBlock and negative toBlock",
   105  			ethereum.FilterQuery{
   106  				Addresses: addresses,
   107  				FromBlock: big.NewInt(-1),
   108  				ToBlock:   big.NewInt(-1),
   109  				Topics:    [][]common.Hash{},
   110  			},
   111  			map[string]interface{}{
   112  				"address":   addresses,
   113  				"fromBlock": "pending",
   114  				"toBlock":   "pending",
   115  				"topics":    [][]common.Hash{},
   116  			},
   117  			nil,
   118  		},
   119  		{
   120  			"with blockhash",
   121  			ethereum.FilterQuery{
   122  				Addresses: addresses,
   123  				BlockHash: &blockHash,
   124  				Topics:    [][]common.Hash{},
   125  			},
   126  			map[string]interface{}{
   127  				"address":   addresses,
   128  				"blockHash": blockHash,
   129  				"topics":    [][]common.Hash{},
   130  			},
   131  			nil,
   132  		},
   133  		{
   134  			"with blockhash and from block",
   135  			ethereum.FilterQuery{
   136  				Addresses: addresses,
   137  				BlockHash: &blockHash,
   138  				FromBlock: big.NewInt(1),
   139  				Topics:    [][]common.Hash{},
   140  			},
   141  			nil,
   142  			blockHashErr,
   143  		},
   144  		{
   145  			"with blockhash and to block",
   146  			ethereum.FilterQuery{
   147  				Addresses: addresses,
   148  				BlockHash: &blockHash,
   149  				ToBlock:   big.NewInt(1),
   150  				Topics:    [][]common.Hash{},
   151  			},
   152  			nil,
   153  			blockHashErr,
   154  		},
   155  		{
   156  			"with blockhash and both from / to block",
   157  			ethereum.FilterQuery{
   158  				Addresses: addresses,
   159  				BlockHash: &blockHash,
   160  				FromBlock: big.NewInt(1),
   161  				ToBlock:   big.NewInt(2),
   162  				Topics:    [][]common.Hash{},
   163  			},
   164  			nil,
   165  			blockHashErr,
   166  		},
   167  	} {
   168  		t.Run(testCase.name, func(t *testing.T) {
   169  			output, err := toFilterArg(testCase.input)
   170  			if (testCase.err == nil) != (err == nil) {
   171  				t.Fatalf("expected error %v but got %v", testCase.err, err)
   172  			}
   173  			if testCase.err != nil {
   174  				if testCase.err.Error() != err.Error() {
   175  					t.Fatalf("expected error %v but got %v", testCase.err, err)
   176  				}
   177  			} else if !reflect.DeepEqual(testCase.output, output) {
   178  				t.Fatalf("expected filter arg %v but got %v", testCase.output, output)
   179  			}
   180  		})
   181  	}
   182  }
   183  
   184  var (
   185  	testKey, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
   186  	testAddr    = crypto.PubkeyToAddress(testKey.PublicKey)
   187  	testBalance = big.NewInt(2e15)
   188  )
   189  
   190  func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
   191  	// Generate test chain.
   192  	genesis, blocks := generateTestChain()
   193  	// Create node
   194  	n, err := node.New(&node.Config{})
   195  	if err != nil {
   196  		t.Fatalf("can't create new node: %v", err)
   197  	}
   198  	// Create Ethereum Service
   199  	config := &ethconfig.Config{Genesis: genesis}
   200  	config.Ethash.PowMode = ethash.ModeFake
   201  	ethservice, err := eth.New(n, config)
   202  	if err != nil {
   203  		t.Fatalf("can't create new ethereum service: %v", err)
   204  	}
   205  	// Import the test chain.
   206  	if err := n.Start(); err != nil {
   207  		t.Fatalf("can't start test node: %v", err)
   208  	}
   209  	if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil {
   210  		t.Fatalf("can't import test blocks: %v", err)
   211  	}
   212  	return n, blocks
   213  }
   214  
   215  func generateTestChain() (*core.Genesis, []*types.Block) {
   216  	db := rawdb.NewMemoryDatabase()
   217  	config := params.AllEthashProtocolChanges
   218  	genesis := &core.Genesis{
   219  		Config:    config,
   220  		Alloc:     core.GenesisAlloc{testAddr: {Balance: testBalance}},
   221  		ExtraData: []byte("test genesis"),
   222  		Timestamp: 9000,
   223  		BaseFee:   big.NewInt(params.InitialBaseFee),
   224  	}
   225  	generate := func(i int, g *core.BlockGen) {
   226  		g.OffsetTime(5)
   227  		g.SetExtra([]byte("test"))
   228  	}
   229  	gblock := genesis.ToBlock(db)
   230  	engine := ethash.NewFaker()
   231  	blocks, _ := core.GenerateChain(config, gblock, engine, db, 1, generate)
   232  	blocks = append([]*types.Block{gblock}, blocks...)
   233  	return genesis, blocks
   234  }
   235  
   236  func TestEthClient(t *testing.T) {
   237  	backend, chain := newTestBackend(t)
   238  	client, _ := backend.Attach()
   239  	defer backend.Close()
   240  	defer client.Close()
   241  
   242  	tests := map[string]struct {
   243  		test func(t *testing.T)
   244  	}{
   245  		"TestHeader": {
   246  			func(t *testing.T) { testHeader(t, chain, client) },
   247  		},
   248  		"TestBalanceAt": {
   249  			func(t *testing.T) { testBalanceAt(t, client) },
   250  		},
   251  		"TestTxInBlockInterrupted": {
   252  			func(t *testing.T) { testTransactionInBlockInterrupted(t, client) },
   253  		},
   254  		"TestChainID": {
   255  			func(t *testing.T) { testChainID(t, client) },
   256  		},
   257  		"TestGetBlock": {
   258  			func(t *testing.T) { testGetBlock(t, client) },
   259  		},
   260  		"TestStatusFunctions": {
   261  			func(t *testing.T) { testStatusFunctions(t, client) },
   262  		},
   263  		"TestCallContract": {
   264  			func(t *testing.T) { testCallContract(t, client) },
   265  		},
   266  		"TestAtFunctions": {
   267  			func(t *testing.T) { testAtFunctions(t, client) },
   268  		},
   269  	}
   270  
   271  	t.Parallel()
   272  	for name, tt := range tests {
   273  		t.Run(name, tt.test)
   274  	}
   275  }
   276  
   277  func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) {
   278  	tests := map[string]struct {
   279  		block   *big.Int
   280  		want    *types.Header
   281  		wantErr error
   282  	}{
   283  		"genesis": {
   284  			block: big.NewInt(0),
   285  			want:  chain[0].Header(),
   286  		},
   287  		"first_block": {
   288  			block: big.NewInt(1),
   289  			want:  chain[1].Header(),
   290  		},
   291  		"future_block": {
   292  			block:   big.NewInt(1000000000),
   293  			want:    nil,
   294  			wantErr: ethereum.NotFound,
   295  		},
   296  	}
   297  	for name, tt := range tests {
   298  		t.Run(name, func(t *testing.T) {
   299  			ec := NewClient(client)
   300  			ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
   301  			defer cancel()
   302  
   303  			got, err := ec.HeaderByNumber(ctx, tt.block)
   304  			if !errors.Is(err, tt.wantErr) {
   305  				t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr)
   306  			}
   307  			if got != nil && got.Number != nil && got.Number.Sign() == 0 {
   308  				got.Number = big.NewInt(0) // hack to make DeepEqual work
   309  			}
   310  			if !reflect.DeepEqual(got, tt.want) {
   311  				t.Fatalf("HeaderByNumber(%v)\n   = %v\nwant %v", tt.block, got, tt.want)
   312  			}
   313  		})
   314  	}
   315  }
   316  
   317  func testBalanceAt(t *testing.T, client *rpc.Client) {
   318  	tests := map[string]struct {
   319  		account common.Address
   320  		block   *big.Int
   321  		want    *big.Int
   322  		wantErr error
   323  	}{
   324  		"valid_account": {
   325  			account: testAddr,
   326  			block:   big.NewInt(1),
   327  			want:    testBalance,
   328  		},
   329  		"non_existent_account": {
   330  			account: common.Address{1},
   331  			block:   big.NewInt(1),
   332  			want:    big.NewInt(0),
   333  		},
   334  		"future_block": {
   335  			account: testAddr,
   336  			block:   big.NewInt(1000000000),
   337  			want:    big.NewInt(0),
   338  			wantErr: errors.New("header not found"),
   339  		},
   340  	}
   341  	for name, tt := range tests {
   342  		t.Run(name, func(t *testing.T) {
   343  			ec := NewClient(client)
   344  			ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
   345  			defer cancel()
   346  
   347  			got, err := ec.BalanceAt(ctx, tt.account, tt.block)
   348  			if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) {
   349  				t.Fatalf("BalanceAt(%x, %v) error = %q, want %q", tt.account, tt.block, err, tt.wantErr)
   350  			}
   351  			if got.Cmp(tt.want) != 0 {
   352  				t.Fatalf("BalanceAt(%x, %v) = %v, want %v", tt.account, tt.block, got, tt.want)
   353  			}
   354  		})
   355  	}
   356  }
   357  
   358  func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) {
   359  	ec := NewClient(client)
   360  
   361  	// Get current block by number
   362  	block, err := ec.BlockByNumber(context.Background(), nil)
   363  	if err != nil {
   364  		t.Fatalf("unexpected error: %v", err)
   365  	}
   366  	// Test tx in block interupted
   367  	ctx, cancel := context.WithCancel(context.Background())
   368  	cancel()
   369  	tx, err := ec.TransactionInBlock(ctx, block.Hash(), 1)
   370  	if tx != nil {
   371  		t.Fatal("transaction should be nil")
   372  	}
   373  	if err == nil || err == ethereum.NotFound {
   374  		t.Fatal("error should not be nil/notfound")
   375  	}
   376  	// Test tx in block not found
   377  	if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 1); err != ethereum.NotFound {
   378  		t.Fatal("error should be ethereum.NotFound")
   379  	}
   380  }
   381  
   382  func testChainID(t *testing.T, client *rpc.Client) {
   383  	ec := NewClient(client)
   384  	id, err := ec.ChainID(context.Background())
   385  	if err != nil {
   386  		t.Fatalf("unexpected error: %v", err)
   387  	}
   388  	if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 {
   389  		t.Fatalf("ChainID returned wrong number: %+v", id)
   390  	}
   391  }
   392  
   393  func testGetBlock(t *testing.T, client *rpc.Client) {
   394  	ec := NewClient(client)
   395  	// Get current block number
   396  	blockNumber, err := ec.BlockNumber(context.Background())
   397  	if err != nil {
   398  		t.Fatalf("unexpected error: %v", err)
   399  	}
   400  	if blockNumber != 1 {
   401  		t.Fatalf("BlockNumber returned wrong number: %d", blockNumber)
   402  	}
   403  	// Get current block by number
   404  	block, err := ec.BlockByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
   405  	if err != nil {
   406  		t.Fatalf("unexpected error: %v", err)
   407  	}
   408  	if block.NumberU64() != blockNumber {
   409  		t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64())
   410  	}
   411  	// Get current block by hash
   412  	blockH, err := ec.BlockByHash(context.Background(), block.Hash())
   413  	if err != nil {
   414  		t.Fatalf("unexpected error: %v", err)
   415  	}
   416  	if block.Hash() != blockH.Hash() {
   417  		t.Fatalf("BlockByHash returned wrong block: want %v got %v", block.Hash().Hex(), blockH.Hash().Hex())
   418  	}
   419  	// Get header by number
   420  	header, err := ec.HeaderByNumber(context.Background(), new(big.Int).SetUint64(blockNumber))
   421  	if err != nil {
   422  		t.Fatalf("unexpected error: %v", err)
   423  	}
   424  	if block.Header().Hash() != header.Hash() {
   425  		t.Fatalf("HeaderByNumber returned wrong header: want %v got %v", block.Header().Hash().Hex(), header.Hash().Hex())
   426  	}
   427  	// Get header by hash
   428  	headerH, err := ec.HeaderByHash(context.Background(), block.Hash())
   429  	if err != nil {
   430  		t.Fatalf("unexpected error: %v", err)
   431  	}
   432  	if block.Header().Hash() != headerH.Hash() {
   433  		t.Fatalf("HeaderByHash returned wrong header: want %v got %v", block.Header().Hash().Hex(), headerH.Hash().Hex())
   434  	}
   435  }
   436  
   437  func testStatusFunctions(t *testing.T, client *rpc.Client) {
   438  	ec := NewClient(client)
   439  
   440  	// Sync progress
   441  	progress, err := ec.SyncProgress(context.Background())
   442  	if err != nil {
   443  		t.Fatalf("unexpected error: %v", err)
   444  	}
   445  	if progress != nil {
   446  		t.Fatalf("unexpected progress: %v", progress)
   447  	}
   448  	// NetworkID
   449  	networkID, err := ec.NetworkID(context.Background())
   450  	if err != nil {
   451  		t.Fatalf("unexpected error: %v", err)
   452  	}
   453  	if networkID.Cmp(big.NewInt(0)) != 0 {
   454  		t.Fatalf("unexpected networkID: %v", networkID)
   455  	}
   456  	// SuggestGasPrice (should suggest 1 Gwei)
   457  	gasPrice, err := ec.SuggestGasPrice(context.Background())
   458  	if err != nil {
   459  		t.Fatalf("unexpected error: %v", err)
   460  	}
   461  	if gasPrice.Cmp(big.NewInt(1875000000)) != 0 { // 1 gwei tip + 0.875 basefee after a 1 gwei fee empty block
   462  		t.Fatalf("unexpected gas price: %v", gasPrice)
   463  	}
   464  	// SuggestGasTipCap (should suggest 1 Gwei)
   465  	gasTipCap, err := ec.SuggestGasTipCap(context.Background())
   466  	if err != nil {
   467  		t.Fatalf("unexpected error: %v", err)
   468  	}
   469  	if gasTipCap.Cmp(big.NewInt(1000000000)) != 0 {
   470  		t.Fatalf("unexpected gas tip cap: %v", gasTipCap)
   471  	}
   472  }
   473  
   474  func testCallContract(t *testing.T, client *rpc.Client) {
   475  	ec := NewClient(client)
   476  
   477  	// EstimateGas
   478  	msg := ethereum.CallMsg{
   479  		From:  testAddr,
   480  		To:    &common.Address{},
   481  		Gas:   21000,
   482  		Value: big.NewInt(1),
   483  	}
   484  	gas, err := ec.EstimateGas(context.Background(), msg)
   485  	if err != nil {
   486  		t.Fatalf("unexpected error: %v", err)
   487  	}
   488  	if gas != 21000 {
   489  		t.Fatalf("unexpected gas price: %v", gas)
   490  	}
   491  	// CallContract
   492  	if _, err := ec.CallContract(context.Background(), msg, big.NewInt(1)); err != nil {
   493  		t.Fatalf("unexpected error: %v", err)
   494  	}
   495  	// PendingCallCOntract
   496  	if _, err := ec.PendingCallContract(context.Background(), msg); err != nil {
   497  		t.Fatalf("unexpected error: %v", err)
   498  	}
   499  }
   500  
   501  func testAtFunctions(t *testing.T, client *rpc.Client) {
   502  	ec := NewClient(client)
   503  	// send a transaction for some interesting pending status
   504  	sendTransaction(ec)
   505  	time.Sleep(100 * time.Millisecond)
   506  	// Check pending transaction count
   507  	pending, err := ec.PendingTransactionCount(context.Background())
   508  	if err != nil {
   509  		t.Fatalf("unexpected error: %v", err)
   510  	}
   511  	if pending != 1 {
   512  		t.Fatalf("unexpected pending, wanted 1 got: %v", pending)
   513  	}
   514  	// Query balance
   515  	balance, err := ec.BalanceAt(context.Background(), testAddr, nil)
   516  	if err != nil {
   517  		t.Fatalf("unexpected error: %v", err)
   518  	}
   519  	penBalance, err := ec.PendingBalanceAt(context.Background(), testAddr)
   520  	if err != nil {
   521  		t.Fatalf("unexpected error: %v", err)
   522  	}
   523  	if balance.Cmp(penBalance) == 0 {
   524  		t.Fatalf("unexpected balance: %v %v", balance, penBalance)
   525  	}
   526  	// NonceAt
   527  	nonce, err := ec.NonceAt(context.Background(), testAddr, nil)
   528  	if err != nil {
   529  		t.Fatalf("unexpected error: %v", err)
   530  	}
   531  	penNonce, err := ec.PendingNonceAt(context.Background(), testAddr)
   532  	if err != nil {
   533  		t.Fatalf("unexpected error: %v", err)
   534  	}
   535  	if penNonce != nonce+1 {
   536  		t.Fatalf("unexpected nonce: %v %v", nonce, penNonce)
   537  	}
   538  	// StorageAt
   539  	storage, err := ec.StorageAt(context.Background(), testAddr, common.Hash{}, nil)
   540  	if err != nil {
   541  		t.Fatalf("unexpected error: %v", err)
   542  	}
   543  	penStorage, err := ec.PendingStorageAt(context.Background(), testAddr, common.Hash{})
   544  	if err != nil {
   545  		t.Fatalf("unexpected error: %v", err)
   546  	}
   547  	if !bytes.Equal(storage, penStorage) {
   548  		t.Fatalf("unexpected storage: %v %v", storage, penStorage)
   549  	}
   550  	// CodeAt
   551  	code, err := ec.CodeAt(context.Background(), testAddr, nil)
   552  	if err != nil {
   553  		t.Fatalf("unexpected error: %v", err)
   554  	}
   555  	penCode, err := ec.PendingCodeAt(context.Background(), testAddr)
   556  	if err != nil {
   557  		t.Fatalf("unexpected error: %v", err)
   558  	}
   559  	if !bytes.Equal(code, penCode) {
   560  		t.Fatalf("unexpected code: %v %v", code, penCode)
   561  	}
   562  }
   563  
   564  func sendTransaction(ec *Client) error {
   565  	// Retrieve chainID
   566  	chainID, err := ec.ChainID(context.Background())
   567  	if err != nil {
   568  		return err
   569  	}
   570  	// Create transaction
   571  	tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(params.InitialBaseFee), nil)
   572  	signer := types.LatestSignerForChainID(chainID)
   573  	signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey)
   574  	if err != nil {
   575  		return err
   576  	}
   577  	signedTx, err := tx.WithSignature(signer, signature)
   578  	if err != nil {
   579  		return err
   580  	}
   581  	// Send transaction
   582  	return ec.SendTransaction(context.Background(), signedTx)
   583  }