// 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 import ( "context" "errors" "fmt" "math/big" "reflect" "testing" "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/eth" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" ) // Verify that Client implements the ethereum interfaces. var ( _ = ethereum.ChainReader(&Client{}) _ = ethereum.TransactionReader(&Client{}) _ = ethereum.ChainStateReader(&Client{}) _ = ethereum.ChainSyncReader(&Client{}) _ = ethereum.ContractCaller(&Client{}) _ = ethereum.GasEstimator(&Client{}) _ = ethereum.GasPricer(&Client{}) _ = ethereum.LogFilterer(&Client{}) _ = ethereum.PendingStateReader(&Client{}) // _ = ethereum.PendingStateEventer(&Client{}) _ = ethereum.PendingContractCaller(&Client{}) ) func TestToFilterArg(t *testing.T) { blockHashErr := fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock") addresses := []common.Address{ common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"), } blockHash := common.HexToHash( "0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb", ) for _, testCase := range []struct { name string input ethereum.FilterQuery output interface{} err error }{ { "without BlockHash", ethereum.FilterQuery{ Addresses: addresses, FromBlock: big.NewInt(1), ToBlock: big.NewInt(2), Topics: [][]common.Hash{}, }, map[string]interface{}{ "address": addresses, "fromBlock": "0x1", "toBlock": "0x2", "topics": [][]common.Hash{}, }, nil, }, { "with nil fromBlock and nil toBlock", ethereum.FilterQuery{ Addresses: addresses, Topics: [][]common.Hash{}, }, map[string]interface{}{ "address": addresses, "fromBlock": "0x0", "toBlock": "latest", "topics": [][]common.Hash{}, }, nil, }, { "with negative fromBlock and negative toBlock", ethereum.FilterQuery{ Addresses: addresses, FromBlock: big.NewInt(-1), ToBlock: big.NewInt(-1), Topics: [][]common.Hash{}, }, map[string]interface{}{ "address": addresses, "fromBlock": "pending", "toBlock": "pending", "topics": [][]common.Hash{}, }, nil, }, { "with blockhash", ethereum.FilterQuery{ Addresses: addresses, BlockHash: &blockHash, Topics: [][]common.Hash{}, }, map[string]interface{}{ "address": addresses, "blockHash": blockHash, "topics": [][]common.Hash{}, }, nil, }, { "with blockhash and from block", ethereum.FilterQuery{ Addresses: addresses, BlockHash: &blockHash, FromBlock: big.NewInt(1), Topics: [][]common.Hash{}, }, nil, blockHashErr, }, { "with blockhash and to block", ethereum.FilterQuery{ Addresses: addresses, BlockHash: &blockHash, ToBlock: big.NewInt(1), Topics: [][]common.Hash{}, }, nil, blockHashErr, }, { "with blockhash and both from / to block", ethereum.FilterQuery{ Addresses: addresses, BlockHash: &blockHash, FromBlock: big.NewInt(1), ToBlock: big.NewInt(2), Topics: [][]common.Hash{}, }, nil, blockHashErr, }, } { t.Run(testCase.name, func(t *testing.T) { output, err := toFilterArg(testCase.input) if (testCase.err == nil) != (err == nil) { t.Fatalf("expected error %v but got %v", testCase.err, err) } if testCase.err != nil { if testCase.err.Error() != err.Error() { t.Fatalf("expected error %v but got %v", testCase.err, err) } } else if !reflect.DeepEqual(testCase.output, output) { t.Fatalf("expected filter arg %v but got %v", testCase.output, output) } }) } } var ( testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testAddr = crypto.PubkeyToAddress(testKey.PublicKey) testBalance = big.NewInt(2e10) ) func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Generate test chain. genesis, blocks := generateTestChain() // Start Ethereum service. var ethservice *eth.Ethereum n, err := node.New(&node.Config{}) n.Register(func(ctx *node.ServiceContext) (node.Service, error) { config := ð.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake ethservice, err = eth.New(ctx, config) return ethservice, err }) // 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 } func generateTestChain() (*core.Genesis, []*types.Block) { db := rawdb.NewMemoryDatabase() config := params.AllEthashProtocolChanges genesis := &core.Genesis{ Config: config, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, ExtraData: []byte("test genesis"), Timestamp: 9000, } generate := func(i int, g *core.BlockGen) { g.OffsetTime(5) g.SetExtra([]byte("test")) } gblock := genesis.ToBlock(db) engine := ethash.NewFaker() blocks, _ := core.GenerateChain(config, gblock, engine, db, 1, generate) blocks = append([]*types.Block{gblock}, blocks...) return genesis, blocks } func TestHeader(t *testing.T) { backend, chain := newTestBackend(t) client, _ := backend.Attach() defer backend.Stop() defer client.Close() 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, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { ec := NewClient(client) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() got, err := ec.HeaderByNumber(ctx, tt.block) if tt.wantErr != nil && (err == nil || err.Error() != tt.wantErr.Error()) { t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr) } if got != nil && got.Number.Sign() == 0 { got.Number = big.NewInt(0) // hack to make DeepEqual work } if !reflect.DeepEqual(got, tt.want) { t.Fatalf("HeaderByNumber(%v)\n = %v\nwant %v", tt.block, got, tt.want) } }) } } func TestBalanceAt(t *testing.T) { backend, _ := newTestBackend(t) client, _ := backend.Attach() defer backend.Stop() defer client.Close() tests := map[string]struct { account common.Address block *big.Int want *big.Int wantErr error }{ "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 not found"), }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { ec := 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 TestTransactionInBlockInterrupted(t *testing.T) { backend, _ := newTestBackend(t) client, _ := backend.Attach() defer backend.Stop() defer client.Close() ec := NewClient(client) ctx, cancel := context.WithCancel(context.Background()) cancel() tx, err := ec.TransactionInBlock(ctx, common.Hash{1}, 1) if tx != nil { t.Fatal("transaction should be nil") } if err == nil { t.Fatal("error should not be nil") } } func TestChainID(t *testing.T) { backend, _ := newTestBackend(t) client, _ := backend.Attach() defer backend.Stop() defer client.Close() ec := NewClient(client) id, err := ec.ChainID(context.Background()) if err != nil { t.Fatalf("unexpected error: %v", err) } if id == nil || id.Cmp(params.AllEthashProtocolChanges.ChainID) != 0 { t.Fatalf("ChainID returned wrong number: %+v", id) } }