// Copyright 2023 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 ethapi import ( "bytes" "context" "crypto/ecdsa" "encoding/json" "errors" "math/big" "reflect" "sort" "testing" "time" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" ) func TestTransaction_RoundTripRpcJSON(t *testing.T) { var ( config = params.AllEthashProtocolChanges signer = types.LatestSigner(config) key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") tests = allTransactionTypes(common.Address{0xde, 0xad}, config) ) t.Parallel() for i, tt := range tests { var tx2 types.Transaction tx, err := types.SignNewTx(key, signer, tt) if err != nil { t.Fatalf("test %d: signing failed: %v", i, err) } // Regular transaction if data, err := json.Marshal(tx); err != nil { t.Fatalf("test %d: marshalling failed; %v", i, err) } else if err = tx2.UnmarshalJSON(data); err != nil { t.Fatalf("test %d: sunmarshal failed: %v", i, err) } else if want, have := tx.Hash(), tx2.Hash(); want != have { t.Fatalf("test %d: stx changed, want %x have %x", i, want, have) } // rpcTransaction rpcTx := newRPCTransaction(tx, common.Hash{}, 0, 0, 0, nil, config) if data, err := json.Marshal(rpcTx); err != nil { t.Fatalf("test %d: marshalling failed; %v", i, err) } else if err = tx2.UnmarshalJSON(data); err != nil { t.Fatalf("test %d: unmarshal failed: %v", i, err) } else if want, have := tx.Hash(), tx2.Hash(); want != have { t.Fatalf("test %d: tx changed, want %x have %x", i, want, have) } } } func allTransactionTypes(addr common.Address, config *params.ChainConfig) []types.TxData { return []types.TxData{ &types.LegacyTx{ Nonce: 5, GasPrice: big.NewInt(6), Gas: 7, To: &addr, Value: big.NewInt(8), Data: []byte{0, 1, 2, 3, 4}, V: big.NewInt(9), R: big.NewInt(10), S: big.NewInt(11), }, &types.LegacyTx{ Nonce: 5, GasPrice: big.NewInt(6), Gas: 7, To: nil, Value: big.NewInt(8), Data: []byte{0, 1, 2, 3, 4}, V: big.NewInt(32), R: big.NewInt(10), S: big.NewInt(11), }, &types.AccessListTx{ ChainID: config.ChainID, Nonce: 5, GasPrice: big.NewInt(6), Gas: 7, To: &addr, Value: big.NewInt(8), Data: []byte{0, 1, 2, 3, 4}, AccessList: types.AccessList{ types.AccessTuple{ Address: common.Address{0x2}, StorageKeys: []common.Hash{types.EmptyRootHash}, }, }, V: big.NewInt(32), R: big.NewInt(10), S: big.NewInt(11), }, &types.AccessListTx{ ChainID: config.ChainID, Nonce: 5, GasPrice: big.NewInt(6), Gas: 7, To: nil, Value: big.NewInt(8), Data: []byte{0, 1, 2, 3, 4}, AccessList: types.AccessList{ types.AccessTuple{ Address: common.Address{0x2}, StorageKeys: []common.Hash{types.EmptyRootHash}, }, }, V: big.NewInt(32), R: big.NewInt(10), S: big.NewInt(11), }, &types.DynamicFeeTx{ ChainID: config.ChainID, Nonce: 5, GasTipCap: big.NewInt(6), GasFeeCap: big.NewInt(9), Gas: 7, To: &addr, Value: big.NewInt(8), Data: []byte{0, 1, 2, 3, 4}, AccessList: types.AccessList{ types.AccessTuple{ Address: common.Address{0x2}, StorageKeys: []common.Hash{types.EmptyRootHash}, }, }, V: big.NewInt(32), R: big.NewInt(10), S: big.NewInt(11), }, &types.DynamicFeeTx{ ChainID: config.ChainID, Nonce: 5, GasTipCap: big.NewInt(6), GasFeeCap: big.NewInt(9), Gas: 7, To: nil, Value: big.NewInt(8), Data: []byte{0, 1, 2, 3, 4}, AccessList: types.AccessList{}, V: big.NewInt(32), R: big.NewInt(10), S: big.NewInt(11), }, } } type testBackend struct { db ethdb.Database chain *core.BlockChain } func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { var ( engine = ethash.NewFaker() backend = &testBackend{ db: rawdb.NewMemoryDatabase(), } cacheConfig = &core.CacheConfig{ TrieCleanLimit: 256, TrieDirtyLimit: 256, TrieTimeLimit: 5 * time.Minute, SnapshotLimit: 0, TrieDirtyDisabled: true, // Archive mode } ) // Generate blocks for testing _, blocks, _ := core.GenerateChainWithGenesis(gspec, engine, n, generator) chain, err := core.NewBlockChain(backend.db, cacheConfig, gspec, nil, engine, vm.Config{}, nil, nil) if err != nil { t.Fatalf("failed to create tester chain: %v", err) } if n, err := chain.InsertChain(blocks); err != nil { t.Fatalf("block %d: failed to insert into chain: %v", n, err) } backend.chain = chain return backend } func (b testBackend) SyncProgress() ethereum.SyncProgress { return ethereum.SyncProgress{} } func (b testBackend) SuggestGasTipCap(ctx context.Context) (*big.Int, error) { return big.NewInt(0), nil } func (b testBackend) FeeHistory(ctx context.Context, blockCount uint64, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*big.Int, [][]*big.Int, []*big.Int, []float64, error) { return nil, nil, nil, nil, nil } func (b testBackend) ChainDb() ethdb.Database { return b.db } func (b testBackend) AccountManager() *accounts.Manager { return nil } func (b testBackend) ExtRPCEnabled() bool { return false } func (b testBackend) RPCGasCap() uint64 { return 10000000 } func (b testBackend) RPCEVMTimeout() time.Duration { return time.Second } func (b testBackend) RPCTxFeeCap() float64 { return 0 } func (b testBackend) UnprotectedAllowed() bool { return false } func (b testBackend) SetHead(number uint64) {} func (b testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { if number == rpc.LatestBlockNumber { return b.chain.CurrentBlock(), nil } return b.chain.GetHeaderByNumber(uint64(number)), nil } func (b testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { panic("implement me") } func (b testBackend) HeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Header, error) { panic("implement me") } func (b testBackend) CurrentHeader() *types.Header { panic("implement me") } func (b testBackend) CurrentBlock() *types.Header { panic("implement me") } func (b testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { if number == rpc.LatestBlockNumber { head := b.chain.CurrentBlock() return b.chain.GetBlock(head.Hash(), head.Number.Uint64()), nil } return b.chain.GetBlockByNumber(uint64(number)), nil } func (b testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { panic("implement me") } func (b testBackend) BlockByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*types.Block, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.BlockByNumber(ctx, blockNr) } panic("implement me") } func (b testBackend) GetBody(ctx context.Context, hash common.Hash, number rpc.BlockNumber) (*types.Body, error) { return b.chain.GetBlock(hash, uint64(number.Int64())).Body(), nil } func (b testBackend) StateAndHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*state.StateDB, *types.Header, error) { if number == rpc.PendingBlockNumber { panic("pending state not implemented") } header, err := b.HeaderByNumber(ctx, number) if err != nil { return nil, nil, err } if header == nil { return nil, nil, errors.New("header not found") } stateDb, err := b.chain.StateAt(header.Root) return stateDb, header, err } func (b testBackend) StateAndHeaderByNumberOrHash(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (*state.StateDB, *types.Header, error) { if blockNr, ok := blockNrOrHash.Number(); ok { return b.StateAndHeaderByNumber(ctx, blockNr) } panic("only implemented for number") } func (b testBackend) PendingBlockAndReceipts() (*types.Block, types.Receipts) { panic("implement me") } func (b testBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) { panic("implement me") } func (b testBackend) GetTd(ctx context.Context, hash common.Hash) *big.Int { panic("implement me") } func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state.StateDB, header *types.Header, vmConfig *vm.Config, blockContext *vm.BlockContext) (*vm.EVM, func() error) { vmError := func() error { return nil } if vmConfig == nil { vmConfig = b.chain.GetVMConfig() } txContext := core.NewEVMTxContext(msg) context := core.NewEVMBlockContext(header, b.chain, nil) if blockContext != nil { context = *blockContext } return vm.NewEVM(context, txContext, state, b.chain.Config(), *vmConfig), vmError } func (b testBackend) SubscribeChainEvent(ch chan<- core.ChainEvent) event.Subscription { panic("implement me") } func (b testBackend) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription { panic("implement me") } func (b testBackend) SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription { panic("implement me") } func (b testBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { panic("implement me") } func (b testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { panic("implement me") } func (b testBackend) GetPoolTransactions() (types.Transactions, error) { panic("implement me") } func (b testBackend) GetPoolTransaction(txHash common.Hash) *types.Transaction { panic("implement me") } func (b testBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { panic("implement me") } func (b testBackend) Stats() (pending int, queued int) { panic("implement me") } func (b testBackend) TxPoolContent() (map[common.Address]types.Transactions, map[common.Address]types.Transactions) { panic("implement me") } func (b testBackend) TxPoolContentFrom(addr common.Address) (types.Transactions, types.Transactions) { panic("implement me") } func (b testBackend) SubscribeNewTxsEvent(events chan<- core.NewTxsEvent) event.Subscription { panic("implement me") } func (b testBackend) ChainConfig() *params.ChainConfig { return b.chain.Config() } func (b testBackend) Engine() consensus.Engine { return b.chain.Engine() } func (b testBackend) GetLogs(ctx context.Context, blockHash common.Hash, number uint64) ([][]*types.Log, error) { panic("implement me") } func (b testBackend) SubscribeRemovedLogsEvent(ch chan<- core.RemovedLogsEvent) event.Subscription { panic("implement me") } func (b testBackend) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscription { panic("implement me") } func (b testBackend) SubscribePendingLogsEvent(ch chan<- []*types.Log) event.Subscription { panic("implement me") } func (b testBackend) BloomStatus() (uint64, uint64) { panic("implement me") } func (b testBackend) ServiceFilter(ctx context.Context, session *bloombits.MatcherSession) { panic("implement me") } func TestEstimateGas(t *testing.T) { t.Parallel() // Initialize test accounts var ( accounts = newAccounts(2) genesis = &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)}, }, } genBlocks = 10 signer = types.HomesteadSigner{} randomAccounts = newAccounts(2) ) api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &accounts[1].addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, accounts[0].key) b.AddTx(tx) })) var testSuite = []struct { blockNumber rpc.BlockNumber call TransactionArgs expectErr error want uint64 }{ // simple transfer on latest block { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, expectErr: nil, want: 21000, }, // simple transfer with insufficient funds on latest block { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &randomAccounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, expectErr: core.ErrInsufficientFunds, want: 21000, }, // empty create { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{}, expectErr: nil, want: 53000, }, } for i, tc := range testSuite { result, err := api.EstimateGas(context.Background(), tc.call, &rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) continue } if !errors.Is(err, tc.expectErr) { t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) } continue } if err != nil { t.Errorf("test %d: want no error, have %v", i, err) continue } if uint64(result) != tc.want { t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, uint64(result), tc.want) } } } func TestCall(t *testing.T) { t.Parallel() // Initialize test accounts var ( accounts = newAccounts(3) genesis = &core.Genesis{ Config: params.TestChainConfig, Alloc: core.GenesisAlloc{ accounts[0].addr: {Balance: big.NewInt(params.Ether)}, accounts[1].addr: {Balance: big.NewInt(params.Ether)}, accounts[2].addr: {Balance: big.NewInt(params.Ether)}, }, } genBlocks = 10 signer = types.HomesteadSigner{} ) api := NewBlockChainAPI(newTestBackend(t, genBlocks, genesis, func(i int, b *core.BlockGen) { // Transfer from account[0] to account[1] // value: 1000 wei // fee: 0 wei tx, _ := types.SignTx(types.NewTx(&types.LegacyTx{Nonce: uint64(i), To: &accounts[1].addr, Value: big.NewInt(1000), Gas: params.TxGas, GasPrice: b.BaseFee(), Data: nil}), signer, accounts[0].key) b.AddTx(tx) })) randomAccounts := newAccounts(3) var testSuite = []struct { blockNumber rpc.BlockNumber overrides StateOverride call TransactionArgs blockOverrides BlockOverrides expectErr error want string }{ // transfer on genesis { blockNumber: rpc.BlockNumber(0), call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, expectErr: nil, want: "0x", }, // transfer on the head { blockNumber: rpc.BlockNumber(genBlocks), call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, expectErr: nil, want: "0x", }, // transfer on a non-existent block, error expects { blockNumber: rpc.BlockNumber(genBlocks + 1), call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, expectErr: errors.New("header not found"), }, // transfer on the latest block { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[0].addr, To: &accounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, expectErr: nil, want: "0x", }, // Call which can only succeed if state is state overridden { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &randomAccounts[0].addr, To: &randomAccounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, overrides: StateOverride{ randomAccounts[0].addr: OverrideAccount{Balance: newRPCBalance(new(big.Int).Mul(big.NewInt(1), big.NewInt(params.Ether)))}, }, want: "0x", }, // Invalid call without state overriding { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &randomAccounts[0].addr, To: &randomAccounts[1].addr, Value: (*hexutil.Big)(big.NewInt(1000)), }, expectErr: core.ErrInsufficientFunds, }, // Successful simple contract call // // // SPDX-License-Identifier: GPL-3.0 // // pragma solidity >=0.7.0 <0.8.0; // // /** // * @title Storage // * @dev Store & retrieve value in a variable // */ // contract Storage { // uint256 public number; // constructor() { // number = block.number; // } // } { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &randomAccounts[0].addr, To: &randomAccounts[2].addr, Data: hex2Bytes("8381f58a"), // call number() }, overrides: StateOverride{ randomAccounts[2].addr: OverrideAccount{ Code: hex2Bytes("6080604052348015600f57600080fd5b506004361060285760003560e01c80638381f58a14602d575b600080fd5b60336049565b6040518082815260200191505060405180910390f35b6000548156fea2646970667358221220eab35ffa6ab2adfe380772a48b8ba78e82a1b820a18fcb6f59aa4efb20a5f60064736f6c63430007040033"), StateDiff: &map[common.Hash]common.Hash{common.Hash{}: common.BigToHash(big.NewInt(123))}, }, }, want: "0x000000000000000000000000000000000000000000000000000000000000007b", }, // Block overrides should work { blockNumber: rpc.LatestBlockNumber, call: TransactionArgs{ From: &accounts[1].addr, Input: &hexutil.Bytes{ 0x43, // NUMBER 0x60, 0x00, 0x52, // MSTORE offset 0 0x60, 0x20, 0x60, 0x00, 0xf3, }, }, blockOverrides: BlockOverrides{Number: (*hexutil.Big)(big.NewInt(11))}, want: "0x000000000000000000000000000000000000000000000000000000000000000b", }, } for i, tc := range testSuite { result, err := api.Call(context.Background(), tc.call, rpc.BlockNumberOrHash{BlockNumber: &tc.blockNumber}, &tc.overrides, &tc.blockOverrides) if tc.expectErr != nil { if err == nil { t.Errorf("test %d: want error %v, have nothing", i, tc.expectErr) continue } if !errors.Is(err, tc.expectErr) { // Second try if !reflect.DeepEqual(err, tc.expectErr) { t.Errorf("test %d: error mismatch, want %v, have %v", i, tc.expectErr, err) } } continue } if err != nil { t.Errorf("test %d: want no error, have %v", i, err) continue } if !reflect.DeepEqual(result.String(), tc.want) { t.Errorf("test %d, result mismatch, have\n%v\n, want\n%v\n", i, result.String(), tc.want) } } } type Account struct { key *ecdsa.PrivateKey addr common.Address } type Accounts []Account func (a Accounts) Len() int { return len(a) } func (a Accounts) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a Accounts) Less(i, j int) bool { return bytes.Compare(a[i].addr.Bytes(), a[j].addr.Bytes()) < 0 } func newAccounts(n int) (accounts Accounts) { for i := 0; i < n; i++ { key, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(key.PublicKey) accounts = append(accounts, Account{key: key, addr: addr}) } sort.Sort(accounts) return accounts } func newRPCBalance(balance *big.Int) **hexutil.Big { rpcBalance := (*hexutil.Big)(balance) return &rpcBalance } func hex2Bytes(str string) *hexutil.Bytes { rpcBytes := hexutil.Bytes(common.Hex2Bytes(str)) return &rpcBytes }