diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index cc65dc4a32f1..7cca28bd8350 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -49,6 +49,7 @@ import ( "github.com/XinFinOrg/XDPoSChain/eth/ethconfig" "github.com/XinFinOrg/XDPoSChain/eth/filters" "github.com/XinFinOrg/XDPoSChain/eth/gasprice" + "github.com/XinFinOrg/XDPoSChain/eth/tracers" "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/ethstats" "github.com/XinFinOrg/XDPoSChain/internal/ethapi" @@ -1644,14 +1645,13 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config, XDCXServ *XDCx. if cfg.SyncMode == downloader.LightSync { Fatalf("can't register eth service in light sync mode, light mode has been deprecated") return nil, nil - } else { - backend, err := eth.New(stack, cfg, XDCXServ, lendingServ) - if err != nil { - Fatalf("Failed to register the Ethereum service: %v", err) - } - - return backend.ApiBackend, backend } + backend, err := eth.New(stack, cfg, XDCXServ, lendingServ) + if err != nil { + Fatalf("Failed to register the Ethereum service: %v", err) + } + stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) + return backend.ApiBackend, backend } // RegisterEthStatsService configures the Ethereum Stats daemon and adds it to the node. diff --git a/core/evm.go b/core/evm.go index 9847a8d790b1..4f42850fee56 100644 --- a/core/evm.go +++ b/core/evm.go @@ -26,8 +26,18 @@ import ( "github.com/XinFinOrg/XDPoSChain/crypto" ) +// ChainContext supports retrieving headers and consensus parameters from the +// current blockchain to be used during transaction processing. +type ChainContext interface { + // Engine retrieves the chain's consensus engine. + Engine() consensus.Engine + + // GetHeader returns the hash corresponding to their hash. + GetHeader(common.Hash, uint64) *types.Header +} + // NewEVMBlockContext creates a new context for use in the EVM. -func NewEVMBlockContext(header *types.Header, chain consensus.ChainContext, author *common.Address) vm.BlockContext { +func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { // If we don't have an explicit author (i.e. not mining), extract from the header var ( beneficiary common.Address @@ -67,7 +77,7 @@ func NewEVMTxContext(msg Message) vm.TxContext { } // GetHashFn returns a GetHashFunc which retrieves header hashes by number -func GetHashFn(ref *types.Header, chain consensus.ChainContext) func(n uint64) common.Hash { +func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash { // Cache will initially contain [refHash.parent], // Then fill up with [refHash.p, refHash.pp, refHash.ppp, ...] var cache []common.Hash diff --git a/eth/api.go b/eth/api.go index fd74041200d9..210cb9cb65cc 100644 --- a/eth/api.go +++ b/eth/api.go @@ -387,8 +387,13 @@ type storageEntry struct { } // StorageRangeAt returns the storage at the given block height and transaction index. -func (api *DebugAPI) StorageRangeAt(blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { - _, _, statedb, err := api.computeTxEnv(blockHash, txIndex, 0) +func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash, txIndex int, contractAddress common.Address, keyStart hexutil.Bytes, maxResult int) (StorageRangeResult, error) { + // Retrieve the block + block := api.eth.blockchain.GetBlockByHash(blockHash) + if block == nil { + return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) + } + _, _, statedb, err := api.eth.stateAtTransaction(block, txIndex, 0) if err != nil { return StorageRangeResult{}, err } diff --git a/eth/api_backend.go b/eth/api_backend.go index 36394ce24fda..df10cb6c2ac1 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -315,6 +315,11 @@ func (b *EthAPIBackend) GetPoolTransaction(hash common.Hash) *types.Transaction return b.eth.txPool.Get(hash) } +func (b *EthAPIBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { + tx, blockHash, blockNumber, index := rawdb.ReadTransaction(b.eth.ChainDb(), txHash) + return tx, blockHash, blockNumber, index, nil +} + func (b *EthAPIBackend) GetPoolNonce(ctx context.Context, addr common.Address) (uint64, error) { return b.eth.txPool.Nonce(addr), nil } @@ -405,6 +410,14 @@ func (b *EthAPIBackend) Miner() *miner.Miner { return b.eth.Miner() } +func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { + return b.eth.stateAtBlock(block, reexec, base, checkLive) +} + +func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { + return b.eth.stateAtTransaction(block, txIndex, reexec) +} + func (b *EthAPIBackend) GetIPCClient() (bind.ContractBackend, error) { // func (b *EthApiBackend) GetIPCClient() (*ethclient.Client, error) { client, err := b.eth.blockchain.GetClient() @@ -419,10 +432,6 @@ func (b *EthAPIBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } -func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { - return b.eth.stateAtBlock(block, reexec, base, checkLive) -} - func (s *EthAPIBackend) GetRewardByHash(hash common.Hash) map[string]map[string]map[string]*big.Int { header := s.eth.blockchain.GetHeaderByHash(hash) if header != nil { diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 0f67b1fd8c35..4f5d221c6739 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -19,9 +19,11 @@ package eth import ( "errors" "fmt" + "math/big" "time" "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/core" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/vm" @@ -59,6 +61,7 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state // Create an ephemeral trie.Database for isolating the live one. Otherwise // the internal junks created by tracing will be persisted into the disk. database = state.NewDatabaseWithConfig(eth.chainDb, &trie.Config{Cache: 16, Preimages: true}) + // If we didn't check the dirty database, do check the clean one, otherwise // we would rewind past a persisted block (specific corner case is chain // tracing from the genesis). @@ -117,7 +120,8 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state // Finalize the state so any modifications are written to the trie root, err := statedb.Commit(eth.blockchain.Config().IsEIP158(current.Number())) if err != nil { - return nil, err + return nil, fmt.Errorf("stateAtBlock commit failed, number %d root %v: %w", + current.NumberU64(), current.Root().Hex(), err) } statedb, err = state.New(root, database) if err != nil { @@ -135,3 +139,54 @@ func (eth *Ethereum) stateAtBlock(block *types.Block, reexec uint64, base *state } return statedb, nil } + +// stateAtTransaction returns the execution environment of a certain transaction. +func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { + // Short circuit if it's genesis block. + if block.NumberU64() == 0 { + return nil, vm.BlockContext{}, nil, errors.New("no transaction in genesis") + } + // Create the parent state database + parent := eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %#x not found", block.ParentHash()) + } + // Lookup the statedb of parent block from the live database, + // otherwise regenerate it on the flight. + statedb, err := eth.stateAtBlock(parent, reexec, nil, true) + if err != nil { + return nil, vm.BlockContext{}, nil, err + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(eth.blockchain.Config(), block.Number()) + feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) + for idx, tx := range block.Transactions() { + var balance *big.Int + if tx.To() != nil { + if value, ok := feeCapacity[*tx.To()]; ok { + balance = value + } + } + // Assemble the transaction call message and return if the requested offset + msg, _ := tx.AsMessage(signer, balance, block.Number(), block.BaseFee()) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) + if idx == txIndex { + return msg, context, statedb, nil + } + // Not yet the searched for transaction, execute on top of the current state + vmenv := vm.NewEVM(context, txContext, statedb, nil, eth.blockchain.Config(), vm.Config{}) + statedb.SetTxContext(tx.Hash(), idx) + owner := common.Address{} + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), owner); err != nil { + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + // Ensure any modifications are committed to the state + // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} diff --git a/eth/api_tracer.go b/eth/tracers/api.go similarity index 53% rename from eth/api_tracer.go rename to eth/tracers/api.go index b45c1a472f56..81d852edfa80 100644 --- a/eth/api_tracer.go +++ b/eth/tracers/api.go @@ -1,4 +1,4 @@ -// Copyright 2017 The go-ethereum Authors +// Copyright 2021 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 @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see . -package eth +package tracers import ( "bytes" @@ -28,20 +28,20 @@ import ( "sync" "time" - "github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate" + "github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common/hexutil" + "github.com/XinFinOrg/XDPoSChain/consensus" "github.com/XinFinOrg/XDPoSChain/core" - "github.com/XinFinOrg/XDPoSChain/core/rawdb" "github.com/XinFinOrg/XDPoSChain/core/state" "github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/vm" - "github.com/XinFinOrg/XDPoSChain/eth/tracers" + "github.com/XinFinOrg/XDPoSChain/ethdb" "github.com/XinFinOrg/XDPoSChain/internal/ethapi" "github.com/XinFinOrg/XDPoSChain/log" + "github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/rlp" "github.com/XinFinOrg/XDPoSChain/rpc" - "github.com/XinFinOrg/XDPoSChain/trie" ) const ( @@ -55,6 +55,105 @@ const ( defaultTraceReexec = uint64(128) ) +// Backend interface provides the common API services (that are provided by +// both full and light clients) with access to necessary functions. +type Backend interface { + AccountManager() *accounts.Manager + HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) + HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) + BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) + GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) + RPCGasCap() uint64 + ChainConfig() *params.ChainConfig + Engine() consensus.Engine + ChainDb() ethdb.Database + StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) + StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) +} + +// API is the collection of tracing APIs exposed over the private debugging endpoint. +type API struct { + backend Backend +} + +// NewAPI creates a new API definition for the tracing methods of the Ethereum service. +func NewAPI(backend Backend) *API { + return &API{backend: backend} +} + +type chainContext struct { + api *API + ctx context.Context +} + +func (context *chainContext) Engine() consensus.Engine { + return context.api.backend.Engine() +} + +func (context *chainContext) GetHeader(hash common.Hash, number uint64) *types.Header { + header, err := context.api.backend.HeaderByNumber(context.ctx, rpc.BlockNumber(number)) + if err != nil { + return nil + } + if header.Hash() == hash { + return header + } + header, err = context.api.backend.HeaderByHash(context.ctx, hash) + if err != nil { + return nil + } + return header +} + +// chainContext construts the context reader which is used by the evm for reading +// the necessary chain context. +func (api *API) chainContext(ctx context.Context) core.ChainContext { + return &chainContext{api: api, ctx: ctx} +} + +// blockByNumber is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + block, err := api.backend.BlockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block #%d not found", number) + } + return block, nil +} + +// blockByHash is the wrapper of the chain access function offered by the backend. +// It will return an error if the block is not found. +func (api *API) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + block, err := api.backend.BlockByHash(ctx, hash) + if err != nil { + return nil, err + } + if block == nil { + return nil, fmt.Errorf("block %s not found", hash.Hex()) + } + return block, nil +} + +// blockByNumberAndHash is the wrapper of the chain access function offered by +// the backend. It will return an error if the block is not found. +// +// Note this function is friendly for the light client which can only retrieve the +// historical(before the CHT) header/block by number. +func (api *API) blockByNumberAndHash(ctx context.Context, number rpc.BlockNumber, hash common.Hash) (*types.Block, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err + } + if block.Hash() == hash { + return block, nil + } + return api.blockByHash(ctx, hash) +} + // TraceConfig holds extra parameters to trace functions. type TraceConfig struct { *vm.LogConfig @@ -103,61 +202,17 @@ type txTraceTask struct { index int // Transaction offset in the block } -// blockByNumber is the wrapper of the chain access function offered by the backend. -// It will return an error if the block is not found. -func (api *DebugAPI) blockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { - block, err := api.eth.ApiBackend.BlockByNumber(ctx, number) +// TraceChain returns the structured logs created during the execution of EVM +// between two blocks (excluding start) and returns them as a JSON object. +func (api *API) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { // Fetch the block interval that we want to trace + from, err := api.blockByNumber(ctx, start) if err != nil { return nil, err } - if block == nil { - return nil, fmt.Errorf("block #%d not found", number) - } - return block, nil -} - -// blockByHash is the wrapper of the chain access function offered by the backend. -// It will return an error if the block is not found. -func (api *DebugAPI) blockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { - block, err := api.eth.ApiBackend.BlockByHash(ctx, hash) + to, err := api.blockByNumber(ctx, end) if err != nil { return nil, err } - if block == nil { - return nil, fmt.Errorf("block %s not found", hash.Hex()) - } - return block, nil -} - -// TraceChain returns the structured logs created during the execution of EVM -// between two blocks (excluding start) and returns them as a JSON object. -func (api *DebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, config *TraceConfig) (*rpc.Subscription, error) { - // Fetch the block interval that we want to trace - var from, to *types.Block - - switch start { - case rpc.PendingBlockNumber: - from = api.eth.blockchain.CurrentBlock() - case rpc.LatestBlockNumber: - from = api.eth.blockchain.CurrentBlock() - default: - from = api.eth.blockchain.GetBlockByNumber(uint64(start)) - } - switch end { - case rpc.PendingBlockNumber: - to = api.eth.blockchain.CurrentBlock() - case rpc.LatestBlockNumber: - to = api.eth.blockchain.CurrentBlock() - default: - to = api.eth.blockchain.GetBlockByNumber(uint64(end)) - } - // Trace the chain if we've found all our blocks - if from == nil { - return nil, fmt.Errorf("starting block #%d not found", start) - } - if to == nil { - return nil, fmt.Errorf("end block #%d not found", end) - } if from.Number().Cmp(to.Number()) >= 0 { return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start) } @@ -167,7 +222,7 @@ func (api *DebugAPI) TraceChain(ctx context.Context, start, end rpc.BlockNumber, // traceChain configures a new tracer according to the provided configuration, and // executes all the transactions contained within. The return value will be one item // per transaction, dependent on the requestd tracer. -func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { +func (api *API) traceChain(ctx context.Context, start, end *types.Block, config *TraceConfig) (*rpc.Subscription, error) { // Tracing a chain is a **long** operation, only do with subscriptions notifier, supported := rpc.NotifierFromContext(ctx) if !supported { @@ -175,58 +230,22 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co } sub := notifier.CreateSubscription() - // Ensure we have a valid starting state before doing any work - origin := start.NumberU64() - database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) - - if number := start.NumberU64(); number > 0 { - start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) - if start == nil { - return nil, fmt.Errorf("parent block #%d not found", number-1) - } - } - statedb, err := state.New(start.Root(), database) - var XDCxState *tradingstate.TradingStateDB - if err != nil { - // If the starting state is missing, allow some number of blocks to be reexecuted - reexec := defaultTraceReexec - if config != nil && config.Reexec != nil { - reexec = *config.Reexec - } - // Find the most recent block that has the state available - for i := uint64(0); i < reexec; i++ { - start = api.eth.blockchain.GetBlock(start.ParentHash(), start.NumberU64()-1) - if start == nil { - break - } - if statedb, err = state.New(start.Root(), database); err == nil { - XDCxState, err = tradingstate.New(start.Root(), tradingstate.NewDatabase(api.eth.XDCX.GetLevelDB())) - if err == nil { - break - } - } - } - // If we still don't have the state available, bail out - if err != nil { - switch err.(type) { - case *trie.MissingNodeError: - return nil, errors.New("required historical state unavailable") - default: - return nil, err - } - } + // Prepare all the states for tracing. Note this procedure can take very + // long time. Timeout mechanism is necessary. + reexec := defaultTraceReexec + if config != nil && config.Reexec != nil { + reexec = *config.Reexec } - // Execute all the transaction contained within the chain concurrently for each block - blocks := int(end.NumberU64() - origin) - + blocks := int(end.NumberU64() - start.NumberU64()) threads := runtime.NumCPU() if threads > blocks { threads = blocks } var ( - pend = new(sync.WaitGroup) - tasks = make(chan *blockTraceTask, threads) - results = make(chan *blockTraceTask, threads) + pend = new(sync.WaitGroup) + tasks = make(chan *blockTraceTask, threads) + results = make(chan *blockTraceTask, threads) + localctx = context.Background() ) for th := 0; th < threads; th++ { pend.Add(1) @@ -235,8 +254,8 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co // Fetch and execute the next block trace tasks for task := range tasks { - signer := types.MakeSigner(api.config, task.block.Number()) - blockCtx := core.NewEVMBlockContext(task.block.Header(), api.eth.blockchain, nil) + signer := types.MakeSigner(api.backend.ChainConfig(), task.block.Number()) + blockCtx := core.NewEVMBlockContext(task.block.Header(), api.chainContext(localctx), nil) feeCapacity := state.GetTRC21FeeCapacityFromState(task.statedb) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { @@ -248,19 +267,19 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co } header := task.block.Header() msg, _ := tx.AsMessage(signer, balance, header.Number, header.BaseFee) - txctx := &tracers.Context{ + txctx := &Context{ BlockHash: task.block.Hash(), TxIndex: i, TxHash: tx.Hash(), } - res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) + res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) break } // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - task.statedb.Finalise(api.eth.blockchain.Config().IsEIP158(task.block.Number())) + task.statedb.Finalise(api.backend.ChainConfig().IsEIP158(task.block.Number())) task.results[i] = &txTraceResult{Result: res} } // Stream the result back to the user or abort on teardown @@ -273,11 +292,12 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co go func() { var ( - logged time.Time - number uint64 - traced uint64 - failed error - proot common.Hash + logged time.Time + number uint64 + traced uint64 + failed error + parent common.Hash + statedb *state.StateDB ) // Ensure everything is properly cleaned up on any exit path defer func() { @@ -295,55 +315,45 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co close(results) }() // Feed all the blocks both into the tracer, as well as fast process concurrently - for number = start.NumberU64() + 1; number <= end.NumberU64(); number++ { + for number = start.NumberU64(); number < end.NumberU64(); number++ { // Print progress logs if long enough time elapsed if time.Since(logged) > 8*time.Second { - if number > origin { - memory, _ := database.TrieDB().Size() - log.Info("Tracing chain segment", "start", origin, "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin), "memory", memory) - } else { - log.Info("Preparing state for chain trace", "block", number, "start", origin, "elapsed", time.Since(begin)) - } logged = time.Now() + log.Info("Tracing chain segment", "start", start.NumberU64(), "end", end.NumberU64(), "current", number, "transactions", traced, "elapsed", time.Since(begin)) } - // Retrieve the next block to trace - block := api.eth.blockchain.GetBlockByNumber(number) - if block == nil { - failed = fmt.Errorf("block #%d not found", number) - break - } - // Send the block over to the concurrent tracers (if not in the fast-forward phase) - if number > origin { - txs := block.Transactions() - - tasks <- &blockTraceTask{statedb: statedb.Copy(), block: block, rootref: proot, results: make([]*txTraceResult, len(txs))} - traced += uint64(len(txs)) - } - feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) - // Generate the next state snapshot fast without tracing - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, XDCxState, vm.Config{}, feeCapacity) + // Retrieve the parent state to trace on top + block, err := api.blockByNumber(localctx, rpc.BlockNumber(number)) if err != nil { failed = err break } - // Finalize the state so any modifications are written to the trie - root, err := statedb.Commit(true) + // Prepare the statedb for tracing. Don't use the live database for + // tracing to avoid persisting state junks into the database. + statedb, err = api.backend.StateAtBlock(localctx, block, reexec, statedb, false) if err != nil { failed = err break } - if err := statedb.Reset(root); err != nil { + if statedb.Database().TrieDB() != nil { + // Hold the reference for tracer, will be released at the final stage + statedb.Database().TrieDB().Reference(block.Root(), common.Hash{}) + + // Release the parent state because it's already held by the tracer + if parent != (common.Hash{}) { + statedb.Database().TrieDB().Dereference(parent) + } + } + parent = block.Root() + + next, err := api.blockByNumber(localctx, rpc.BlockNumber(number+1)) + if err != nil { failed = err break } - // Reference the trie twice, once for us, once for the trancer - database.TrieDB().Reference(root, common.Hash{}) - if number >= origin { - database.TrieDB().Reference(root, common.Hash{}) - } - // Dereference all past tries we ourselves are done working with - database.TrieDB().Dereference(proot) - proot = root + // Send the block over to the concurrent tracers (if not in the fast-forward phase) + txs := next.Transactions() + tasks <- &blockTraceTask{statedb: statedb.Copy(), block: next, rootref: block.Root(), results: make([]*txTraceResult, len(txs))} + traced += uint64(len(txs)) } }() @@ -351,7 +361,7 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co go func() { var ( done = make(map[uint64]*blockTraceResult) - next = origin + 1 + next = start.NumberU64() + 1 ) for res := range results { // Queue up next received result @@ -362,9 +372,10 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co } done[uint64(result.Block)] = result - // Dereference any paret tries held in memory by this task - database.TrieDB().Dereference(res.rootref) - + // Dereference any parent tries held in memory by this task + if res.statedb.Database().TrieDB() != nil { + res.statedb.Database().TrieDB().Dereference(res.rootref) + } // Stream completed traces to the user, aborting on the first error for result, ok := done[next]; ok; result, ok = done[next] { if len(result.Traces) > 0 || next == end.NumberU64() { @@ -380,38 +391,27 @@ func (api *DebugAPI) traceChain(ctx context.Context, start, end *types.Block, co // TraceBlockByNumber returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *DebugAPI) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { - // Fetch the block that we want to trace - var block *types.Block - - switch number { - case rpc.PendingBlockNumber: - block = api.eth.blockchain.CurrentBlock() - case rpc.LatestBlockNumber: - block = api.eth.blockchain.CurrentBlock() - default: - block = api.eth.blockchain.GetBlockByNumber(uint64(number)) - } - // Trace the block if it was found - if block == nil { - return nil, fmt.Errorf("block #%d not found", number) +func (api *API) TraceBlockByNumber(ctx context.Context, number rpc.BlockNumber, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByNumber(ctx, number) + if err != nil { + return nil, err } return api.traceBlock(ctx, block, config) } // TraceBlockByHash returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *DebugAPI) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { - block := api.eth.blockchain.GetBlockByHash(hash) - if block == nil { - return nil, fmt.Errorf("block #%x not found", hash) +func (api *API) TraceBlockByHash(ctx context.Context, hash common.Hash, config *TraceConfig) ([]*txTraceResult, error) { + block, err := api.blockByHash(ctx, hash) + if err != nil { + return nil, err } return api.traceBlock(ctx, block, config) } // TraceBlock returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (api *DebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) { +func (api *API) TraceBlock(ctx context.Context, blob []byte, config *TraceConfig) ([]*txTraceResult, error) { block := new(types.Block) if err := rlp.Decode(bytes.NewReader(blob), block); err != nil { return nil, fmt.Errorf("could not decode block: %v", err) @@ -421,7 +421,7 @@ func (api *DebugAPI) TraceBlock(ctx context.Context, blob []byte, config *TraceC // TraceBlockFromFile returns the structured logs created during the execution of // EVM and returns them as a JSON object. -func (api *DebugAPI) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { +func (api *API) TraceBlockFromFile(ctx context.Context, file string, config *TraceConfig) ([]*txTraceResult, error) { blob, err := os.ReadFile(file) if err != nil { return nil, fmt.Errorf("could not read file: %v", err) @@ -432,28 +432,29 @@ func (api *DebugAPI) TraceBlockFromFile(ctx context.Context, file string, config // traceBlock configures a new tracer according to the provided configuration, and // executes all the transactions contained within. The return value will be one item // per transaction, dependent on the requestd tracer. -func (api *DebugAPI) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { +func (api *API) traceBlock(ctx context.Context, block *types.Block, config *TraceConfig) ([]*txTraceResult, error) { // Skip VerifyHeader to fix #1185 // if err := api.eth.engine.VerifyHeader(api.eth.blockchain, block.Header(), true); err != nil { // return nil, err // } - // Create the parent state database - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, fmt.Errorf("parent %x not found", block.ParentHash()) + if block.NumberU64() == 0 { + return nil, errors.New("genesis is not traceable") + } + parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash()) + if err != nil { + return nil, err } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, XDCxState, err := api.computeStateDB(parent, reexec) + statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true) if err != nil { return nil, err } // Execute all the transaction contained within the block concurrently var ( - signer = types.MakeSigner(api.config, block.Number()) - + signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) txs = block.Transactions() results = make([]*txTraceResult, len(txs)) @@ -465,7 +466,7 @@ func (api *DebugAPI) traceBlock(ctx context.Context, block *types.Block, config threads = len(txs) } blockHash := block.Hash() - blockCtx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) for th := 0; th < threads; th++ { pend.Add(1) go func() { @@ -481,7 +482,7 @@ func (api *DebugAPI) traceBlock(ctx context.Context, block *types.Block, config } header := block.Header() msg, _ := txs[task.index].AsMessage(signer, balance, header.Number, header.BaseFee) - txctx := &tracers.Context{ + txctx := &Context{ BlockHash: blockHash, TxIndex: task.index, TxHash: txs[task.index].Hash(), @@ -514,10 +515,8 @@ func (api *DebugAPI) traceBlock(ctx context.Context, block *types.Block, config // Generate the next state snapshot fast without tracing header := block.Header() msg, _ := tx.AsMessage(signer, balance, header.Number, header.BaseFee) - txContext := core.NewEVMTxContext(msg) statedb.SetTxContext(tx.Hash(), i) - - vmenv := vm.NewEVM(blockCtx, txContext, statedb, XDCxState, api.config, vm.Config{}) + vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, nil, api.backend.ChainConfig(), vm.Config{}) owner := common.Address{} if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), owner); err != nil { failed = err @@ -537,103 +536,30 @@ func (api *DebugAPI) traceBlock(ctx context.Context, block *types.Block, config return results, nil } -// computeStateDB retrieves the state database associated with a certain block. -// If no state is locally available for the given block, a number of blocks are -// attempted to be reexecuted to generate the desired state. -func (api *DebugAPI) computeStateDB(block *types.Block, reexec uint64) (*state.StateDB, *tradingstate.TradingStateDB, error) { - // If we have the state fully available, use that - statedb, err := api.eth.blockchain.StateAt(block.Root()) - XDCxState := &tradingstate.TradingStateDB{} - if err == nil { - XDCxState, err = api.eth.blockchain.OrderStateAt(block) - if err == nil { - return statedb, XDCxState, nil - } - } - // Otherwise try to reexec blocks until we find a state or reach our limit - origin := block.NumberU64() - database := state.NewDatabaseWithConfig(api.eth.ChainDb(), &trie.Config{Cache: 16, Preimages: true}) - - for i := uint64(0); i < reexec; i++ { - block = api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if block == nil { - break - } - if statedb, err = state.New(block.Root(), database); err == nil { - XDCxState, err = api.eth.blockchain.OrderStateAt(block) - if err == nil { - break - } - } - } - if err != nil { - switch err.(type) { - case *trie.MissingNodeError: - return nil, nil, errors.New("required historical state unavailable") - default: - return nil, nil, err - } - } - // State was available at historical point, regenerate - var ( - start = time.Now() - logged time.Time - proot common.Hash - ) - for block.NumberU64() < origin { - // Print progress logs if long enough time elapsed - if time.Since(logged) > 8*time.Second { - log.Info("Regenerating historical state", "block", block.NumberU64()+1, "target", origin, "elapsed", time.Since(start)) - logged = time.Now() - } - // Retrieve the next block to regenerate and process it - if block = api.eth.blockchain.GetBlockByNumber(block.NumberU64() + 1); block == nil { - return nil, nil, fmt.Errorf("block #%d not found", block.NumberU64()+1) - } - feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) - _, _, _, err := api.eth.blockchain.Processor().Process(block, statedb, XDCxState, vm.Config{}, feeCapacity) - if err != nil { - return nil, nil, err - } - root := statedb.IntermediateRoot(true) - if root != block.Root() { - return nil, nil, fmt.Errorf("invalid merkle root (number %d : got : %x expect: %x)", block.NumberU64(), root.Hex(), block.Root()) - } - // Finalize the state so any modifications are written to the trie - root, err = statedb.Commit(true) - if err != nil { - return nil, nil, err - } - if err := statedb.Reset(root); err != nil { - return nil, nil, err - } - database.TrieDB().Reference(root, common.Hash{}) - database.TrieDB().Dereference(proot) - proot = root - } - size, _ := database.TrieDB().Size() - log.Info("Historical state regenerated", "block", block.NumberU64(), "elapsed", time.Since(start), "size", size) - return statedb, XDCxState, nil -} - // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. -func (api *DebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - // Retrieve the transaction and assemble its EVM context - tx, blockHash, _, index := rawdb.ReadTransaction(api.eth.ChainDb(), hash) - if tx == nil { - return nil, fmt.Errorf("transaction %x not found", hash) +func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { + _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + if err != nil { + return nil, err + } + // It shouldn't happen in practice. + if blockNumber == 0 { + return nil, errors.New("genesis is not traceable") } reexec := defaultTraceReexec if config != nil && config.Reexec != nil { reexec = *config.Reexec } - msg, vmctx, statedb, err := api.computeTxEnv(blockHash, int(index), reexec) + block, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(blockNumber), blockHash) if err != nil { return nil, err } - - txctx := &tracers.Context{ + msg, vmctx, statedb, err := api.backend.StateAtTransaction(ctx, block, int(index), reexec) + if err != nil { + return nil, err + } + txctx := &Context{ BlockHash: blockHash, TxIndex: int(index), TxHash: hash, @@ -645,7 +571,7 @@ func (api *DebugAPI) TraceTransaction(ctx context.Context, hash common.Hash, con // created during the execution of EVM if the given transaction was added on // top of the provided block and returns them as a JSON object. // You can provide -2 as a block number to trace on top of the pending block. -func (api *DebugAPI) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { +func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, config *TraceCallConfig) (interface{}, error) { // Try to retrieve the specified block var ( err error @@ -674,7 +600,7 @@ func (api *DebugAPI) TraceCall(ctx context.Context, args ethapi.TransactionArgs, if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.eth.ApiBackend.StateAtBlock(ctx, block, reexec, nil, true) + statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true) if err != nil { return nil, err } @@ -685,24 +611,24 @@ func (api *DebugAPI) TraceCall(ctx context.Context, args ethapi.TransactionArgs, } } // Execute the trace - // TODO: replace block.BaseFee() with vmctx.BaseFee + // TODO(daniel): replace block.BaseFee() with vmctx.BaseFee // reference: https://github.com/ethereum/go-ethereum/pull/29051 - msg, err := args.ToMessage(api.eth.ApiBackend, block.Number(), api.eth.ApiBackend.RPCGasCap(), block.BaseFee()) + msg, err := args.ToMessage(api.backend, block.Number(), api.backend.RPCGasCap(), block.BaseFee()) if err != nil { return nil, err } - vmctx := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) var traceConfig *TraceConfig if config != nil { traceConfig = &config.TraceConfig } - return api.traceTx(ctx, msg, new(tracers.Context), vmctx, statedb, traceConfig) + return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *DebugAPI) traceTx(ctx context.Context, message core.Message, txctx *tracers.Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.EVMLogger @@ -720,7 +646,7 @@ func (api *DebugAPI) traceTx(ctx context.Context, message core.Message, txctx *t return nil, err } } - if t, err := tracers.New(*config.Tracer, txContext, txctx, config.TracerConfig); err != nil { + if t, err := New(*config.Tracer, txContext, txctx, config.TracerConfig); err != nil { return nil, err } else { deadlineCtx, cancel := context.WithTimeout(ctx, timeout) @@ -733,12 +659,11 @@ func (api *DebugAPI) traceTx(ctx context.Context, message core.Message, txctx *t defer cancel() tracer = t } - default: tracer = vm.NewStructLogger(config.LogConfig) } // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, txContext, statedb, nil, api.config, vm.Config{Tracer: tracer, NoBaseFee: true}) + vmenv := vm.NewEVM(vmctx, txContext, statedb, nil, api.backend.ChainConfig(), vm.Config{Tracer: tracer, NoBaseFee: true}) // Call SetTxContext to clear out the statedb access list statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) @@ -748,6 +673,7 @@ func (api *DebugAPI) traceTx(ctx context.Context, message core.Message, txctx *t if err != nil { return nil, fmt.Errorf("tracing failed: %v", err) } + // Depending on the tracer type, format and return the output switch tracer := tracer.(type) { case *vm.StructLogger: @@ -758,7 +684,7 @@ func (api *DebugAPI) traceTx(ctx context.Context, message core.Message, txctx *t StructLogs: ethapi.FormatLogs(tracer.StructLogs()), }, nil - case tracers.Tracer: + case Tracer: return tracer.GetResult() default: @@ -766,63 +692,13 @@ func (api *DebugAPI) traceTx(ctx context.Context, message core.Message, txctx *t } } -// computeTxEnv returns the execution environment of a certain transaction. -func (api *DebugAPI) computeTxEnv(blockHash common.Hash, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { - // Create the parent state database - block := api.eth.blockchain.GetBlockByHash(blockHash) - if block == nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("block %x not found", blockHash) - } - parent := api.eth.blockchain.GetBlock(block.ParentHash(), block.NumberU64()-1) - if parent == nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("parent %x not found", block.ParentHash()) - } - statedb, XDCxState, err := api.computeStateDB(parent, reexec) - if err != nil { - return nil, vm.BlockContext{}, nil, err - } - // Recompute transactions up to the target index. - feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) - if common.TIPSigning.Cmp(block.Header().Number) == 0 { - statedb.DeleteAddress(common.BlockSignersBinary) - } - core.InitSignerInTransactions(api.config, block.Header(), block.Transactions()) - balanceUpdated := map[common.Address]*big.Int{} - totalFeeUsed := big.NewInt(0) - gp := new(core.GasPool).AddGas(block.GasLimit()) - usedGas := new(uint64) - // Iterate over and process the individual transactions - for idx, tx := range block.Transactions() { - statedb.SetTxContext(tx.Hash(), idx) - if idx == txIndex { - var balanceFee *big.Int - if tx.To() != nil { - if value, ok := feeCapacity[*tx.To()]; ok { - balanceFee = value - } - } - header := block.Header() - msg, err := tx.AsMessage(types.MakeSigner(api.config, header.Number), balanceFee, header.Number, header.BaseFee) - if err != nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) - } - context := core.NewEVMBlockContext(block.Header(), api.eth.blockchain, nil) - return msg, context, statedb, nil - } - _, gas, err, tokenFeeUsed := core.ApplyTransaction(api.config, feeCapacity, api.eth.blockchain, nil, gp, statedb, XDCxState, block.Header(), tx, usedGas, vm.Config{}) - if err != nil { - return nil, vm.BlockContext{}, nil, fmt.Errorf("tx %x failed: %v", tx.Hash(), err) - } - - if tokenFeeUsed { - fee := common.GetGasFee(block.Header().Number.Uint64(), gas) - feeCapacity[*tx.To()] = new(big.Int).Sub(feeCapacity[*tx.To()], fee) - balanceUpdated[*tx.To()] = feeCapacity[*tx.To()] - totalFeeUsed = totalFeeUsed.Add(totalFeeUsed, fee) - } +// APIs return the collection of RPC services the tracer package offers. +func APIs(backend Backend) []rpc.API { + // Append all the local APIs and return + return []rpc.API{ + { + Namespace: "debug", + Service: NewAPI(backend), + }, } - // Ensure any modifications are committed to the state - // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect - statedb.Finalise(api.eth.blockchain.Config().IsEIP158(block.Number())) - return nil, vm.BlockContext{}, nil, fmt.Errorf("tx index %d out of range for block %x", txIndex, blockHash) } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go new file mode 100644 index 000000000000..a14b5e5dcbf5 --- /dev/null +++ b/eth/tracers/api_test.go @@ -0,0 +1,471 @@ +// Copyright 2021 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 tracers + +import ( + "bytes" + "context" + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + "reflect" + "sort" + "testing" + "time" + + "github.com/XinFinOrg/XDPoSChain/accounts" + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/common/hexutil" + "github.com/XinFinOrg/XDPoSChain/consensus" + "github.com/XinFinOrg/XDPoSChain/consensus/ethash" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/core/state" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" + "github.com/XinFinOrg/XDPoSChain/crypto" + "github.com/XinFinOrg/XDPoSChain/ethdb" + "github.com/XinFinOrg/XDPoSChain/internal/ethapi" + "github.com/XinFinOrg/XDPoSChain/params" + "github.com/XinFinOrg/XDPoSChain/rpc" +) + +var ( + errStateNotFound = errors.New("state not found") + errBlockNotFound = errors.New("block not found") + errTransactionNotFound = errors.New("transaction not found") +) + +type testBackend struct { + chainConfig *params.ChainConfig + engine consensus.Engine + chaindb ethdb.Database + chain *core.BlockChain +} + +func newTestBackend(t *testing.T, n int, gspec *core.Genesis, generator func(i int, b *core.BlockGen)) *testBackend { + backend := &testBackend{ + chainConfig: params.TestChainConfig, + engine: ethash.NewFaker(), + chaindb: rawdb.NewMemoryDatabase(), + } + // Generate blocks for testing + gspec.Config = backend.chainConfig + var ( + gendb = rawdb.NewMemoryDatabase() + genesis = gspec.MustCommit(gendb) + ) + blocks, _ := core.GenerateChain(backend.chainConfig, genesis, backend.engine, gendb, n, generator) + + // Import the canonical chain + gspec.MustCommit(backend.chaindb) + cacheConfig := &core.CacheConfig{ + TrieCleanLimit: 256, + TrieDirtyLimit: 256, + TrieTimeLimit: 5 * time.Minute, + TrieDirtyDisabled: true, // Archive mode + } + chain, err := core.NewBlockChain(backend.chaindb, cacheConfig, backend.chainConfig, backend.engine, vm.Config{}) + 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) AccountManager() *accounts.Manager { + return nil // Not used in tests +} + +func (b *testBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) { + return b.chain.GetHeaderByHash(hash), nil +} + +func (b *testBackend) HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.CurrentHeader(), nil + } + return b.chain.GetHeaderByNumber(uint64(number)), nil +} + +func (b *testBackend) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { + return b.chain.GetBlockByHash(hash), nil +} + +func (b *testBackend) BlockByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Block, error) { + if number == rpc.PendingBlockNumber || number == rpc.LatestBlockNumber { + return b.chain.CurrentBlock(), nil + } + return b.chain.GetBlockByNumber(uint64(number)), nil +} + +func (b *testBackend) GetTransaction(ctx context.Context, txHash common.Hash) (*types.Transaction, common.Hash, uint64, uint64, error) { + tx, hash, blockNumber, index := rawdb.ReadTransaction(b.chaindb, txHash) + if tx == nil { + return nil, common.Hash{}, 0, 0, errTransactionNotFound + } + return tx, hash, blockNumber, index, nil +} + +func (b *testBackend) RPCGasCap() uint64 { + return 25000000 +} + +func (b *testBackend) ChainConfig() *params.ChainConfig { + return b.chainConfig +} + +func (b *testBackend) Engine() consensus.Engine { + return b.engine +} + +func (b *testBackend) ChainDb() ethdb.Database { + return b.chaindb +} + +func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool) (*state.StateDB, error) { + statedb, err := b.chain.StateAt(block.Root()) + if err != nil { + return nil, errStateNotFound + } + return statedb, nil +} + +func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, error) { + parent := b.chain.GetBlock(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, vm.BlockContext{}, nil, errBlockNotFound + } + statedb, err := b.chain.StateAt(parent.Root()) + if err != nil { + return nil, vm.BlockContext{}, nil, errStateNotFound + } + if txIndex == 0 && len(block.Transactions()) == 0 { + return nil, vm.BlockContext{}, statedb, nil + } + // Recompute transactions up to the target index. + signer := types.MakeSigner(b.chainConfig, block.Number()) + for idx, tx := range block.Transactions() { + msg, _ := tx.AsMessage(signer, nil, block.Number(), block.BaseFee()) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), b.chain, nil) + if idx == txIndex { + return msg, context, statedb, nil + } + vmenv := vm.NewEVM(context, txContext, statedb, nil, b.chainConfig, vm.Config{}) + owner := common.Address{} + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), owner); err != nil { + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + } + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction index %d out of range for block %#x", txIndex, block.Hash()) +} + +func TestTraceCall(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(9000000000000000000)}, + accounts[1].addr: {Balance: big.NewInt(9000000000000000000)}, + accounts[2].addr: {Balance: big.NewInt(9000000000000000000)}, + }} + genBlocks := 10 + signer := types.HomesteadSigner{} + api := NewAPI(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.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + })) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + call ethapi.TransactionArgs + config *TraceCallConfig + expectErr error + expect interface{} + }{ + // Standard JSON trace upon the genesis, plain transfer. + { + blockNumber: rpc.BlockNumber(0), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the head, plain transfer. + { + blockNumber: rpc.BlockNumber(genBlocks), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Standard JSON trace upon the non-existent block, error expects + { + blockNumber: rpc.BlockNumber(genBlocks + 1), + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + expect: nil, + }, + // Standard JSON trace upon the latest block + { + blockNumber: rpc.LatestBlockNumber, + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: nil, + expect: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + // Tracing on 'pending' should fail + { + blockNumber: rpc.PendingBlockNumber, + call: ethapi.TransactionArgs{ + From: &accounts[0].addr, + To: &accounts[1].addr, + Value: (*hexutil.Big)(big.NewInt(1000)), + }, + config: nil, + expectErr: errors.New("tracing on top of pending is not supported"), + expect: nil, + }, + } + for _, testspec := range testSuite { + result, err := api.TraceCall(context.Background(), testspec.call, rpc.BlockNumberOrHash{BlockNumber: &testspec.blockNumber}, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("Expect error %v, get nothing", testspec.expectErr) + continue + } + if !reflect.DeepEqual(err.Error(), testspec.expectErr.Error()) { + t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err) + } + } else { + if err != nil { + t.Errorf("Expect no error, get %v", err) + continue + } + if !reflect.DeepEqual(result, testspec.expect) { + t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result) + } + } + } +} + +func TestTraceTransaction(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(2) + genesis := &core.Genesis{Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(9000000000000000000)}, + accounts[1].addr: {Balance: big.NewInt(9000000000000000000)}, + }} + target := common.Hash{} + signer := types.HomesteadSigner{} + api := NewAPI(newTestBackend(t, 1, genesis, func(i int, b *core.BlockGen) { + // Transfer from account[0] to account[1] + // value: 1000 wei + // fee: 0 wei + tx, _ := types.SignTx(types.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + target = tx.Hash() + })) + result, err := api.TraceTransaction(context.Background(), target, nil) + if err != nil { + t.Errorf("Failed to trace transaction %v", err) + } + if !reflect.DeepEqual(result, ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }) { + t.Error("Transaction tracing result is different") + } +} + +func TestTraceBlock(t *testing.T) { + t.Parallel() + + // Initialize test accounts + accounts := newAccounts(3) + genesis := &core.Genesis{Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(9000000000000000000)}, + accounts[1].addr: {Balance: big.NewInt(9000000000000000000)}, + accounts[2].addr: {Balance: big.NewInt(9000000000000000000)}, + }} + genBlocks := 10 + signer := types.HomesteadSigner{} + api := NewAPI(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.NewTransaction(uint64(i), accounts[1].addr, big.NewInt(1000), params.TxGas, big.NewInt(0), nil), signer, accounts[0].key) + b.AddTx(tx) + })) + + var testSuite = []struct { + blockNumber rpc.BlockNumber + config *TraceConfig + expect interface{} + expectErr error + }{ + // Trace genesis block, expect error + { + blockNumber: rpc.BlockNumber(0), + config: nil, + expect: nil, + expectErr: errors.New("genesis is not traceable"), + }, + // Trace head block + { + blockNumber: rpc.BlockNumber(genBlocks), + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + // Trace non-existent block + { + blockNumber: rpc.BlockNumber(genBlocks + 1), + config: nil, + expectErr: fmt.Errorf("block #%d not found", genBlocks+1), + expect: nil, + }, + // Trace latest block + { + blockNumber: rpc.LatestBlockNumber, + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + // Tracing on 'pending' should fail: + { + blockNumber: rpc.PendingBlockNumber, + config: nil, + expectErr: nil, + expect: []*txTraceResult{ + { + Result: ðapi.ExecutionResult{ + Gas: params.TxGas, + Failed: false, + ReturnValue: "", + StructLogs: []ethapi.StructLogRes{}, + }, + }, + }, + }, + } + for _, testspec := range testSuite { + result, err := api.TraceBlockByNumber(context.Background(), testspec.blockNumber, testspec.config) + if testspec.expectErr != nil { + if err == nil { + t.Errorf("Expect error %v, get nothing", testspec.expectErr) + continue + } + if !reflect.DeepEqual(err, testspec.expectErr) { + t.Errorf("Error mismatch, want %v, get %v", testspec.expectErr, err) + } + } else { + if err != nil { + t.Errorf("Expect no error, get %v", err) + continue + } + if !reflect.DeepEqual(result, testspec.expect) { + t.Errorf("Result mismatch, want %v, get %v", testspec.expect, result) + } + } + } +} + +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 +} diff --git a/internal/ethapi/transaction_args.go b/internal/ethapi/transaction_args.go index 68807629a20d..627f57b68716 100644 --- a/internal/ethapi/transaction_args.go +++ b/internal/ethapi/transaction_args.go @@ -24,6 +24,7 @@ import ( "math" "math/big" + "github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common/hexutil" "github.com/XinFinOrg/XDPoSChain/core/types" @@ -220,10 +221,14 @@ func (args *TransactionArgs) setLondonFeeDefaults(ctx context.Context, head *typ return nil } +type AccountBackend interface { + AccountManager() *accounts.Manager +} + // ToMessage converts the transaction arguments to the Message type used by the // core evm. This method is used in calls and traces that do not require a real // live transaction. -func (args *TransactionArgs) ToMessage(b Backend, number *big.Int, globalGasCap uint64, baseFee *big.Int) (types.Message, error) { +func (args *TransactionArgs) ToMessage(b AccountBackend, number *big.Int, globalGasCap uint64, baseFee *big.Int) (types.Message, error) { // Reject invalid combinations of pre- and post-1559 fee styles if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { return types.Message{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")