@ -17,14 +17,34 @@
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 ) {
@ -157,3 +177,450 @@ func allTransactionTypes(addr common.Address, config *params.ChainConfig) []type
} ,
}
}
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 , 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 , nil
}
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
}