diff --git a/jsonrpc/debug_endpoint.go b/jsonrpc/debug_endpoint.go index 1219e5739b..9c58e2a65b 100644 --- a/jsonrpc/debug_endpoint.go +++ b/jsonrpc/debug_endpoint.go @@ -70,6 +70,10 @@ type debugBlockchainStore interface { // TraceBlock traces all transactions in the given block TraceBlock(*types.Block, tracer.Tracer) ([]interface{}, error) + // IntermediateRoots executes a block (bad- or canon- or side-), and returns a list + // of intermediate roots: the stateroot after each transaction. + IntermediateRoots(*types.Block, tracer.Tracer) ([]interface{}, error) + // TraceTxn traces a transaction in the block, associated with the given hash TraceTxn(*types.Block, types.Hash, tracer.Tracer) (interface{}, error) @@ -723,6 +727,36 @@ func (d *Debug) DumpBlock(blockNumber BlockNumber) (interface{}, error) { ) } +// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list +// of intermediate roots: the stateroot after each transaction. +func (d *Debug) IntermediateRoots( + blockHash types.Hash, + config *TraceConfig, +) (interface{}, error) { + return d.throttling.AttemptRequest( + context.Background(), + func() (interface{}, error) { + block, ok := d.store.GetBlockByHash(blockHash, true) + if !ok { + return nil, fmt.Errorf("block %s not found", blockHash) + } + + if block.Number() == 0 { + return nil, ErrTraceGenesisBlock + } + + tracer, cancel, err := newTracer(config) + if err != nil { + return nil, err + } + + defer cancel() + + return d.store.IntermediateRoots(block, tracer) + }, + ) +} + func (d *Debug) traceBlock( block *types.Block, config *TraceConfig, diff --git a/jsonrpc/debug_endpoint_test.go b/jsonrpc/debug_endpoint_test.go index 639c881a29..e7a2d334f3 100644 --- a/jsonrpc/debug_endpoint_test.go +++ b/jsonrpc/debug_endpoint_test.go @@ -28,6 +28,7 @@ type debugEndpointMockStore struct { getBlockByHashFn func(types.Hash, bool) (*types.Block, bool) getBlockByNumberFn func(uint64, bool) (*types.Block, bool) traceBlockFn func(*types.Block, tracer.Tracer) ([]interface{}, error) + intermediateRootsFn func(*types.Block, tracer.Tracer) ([]interface{}, error) traceTxnFn func(*types.Block, types.Hash, tracer.Tracer) (interface{}, error) traceCallFn func(*types.Transaction, *types.Header, tracer.Tracer) (interface{}, error) getNonceFn func(types.Address) uint64 @@ -74,6 +75,10 @@ func (s *debugEndpointMockStore) TraceBlock(block *types.Block, tracer tracer.Tr return s.traceBlockFn(block, tracer) } +func (s *debugEndpointMockStore) IntermediateRoots(block *types.Block, tracer tracer.Tracer) ([]interface{}, error) { + return s.intermediateRootsFn(block, tracer) +} + func (s *debugEndpointMockStore) TraceTxn(block *types.Block, targetTx types.Hash, tracer tracer.Tracer) (interface{}, error) { return s.traceTxnFn(block, targetTx, tracer) } diff --git a/server/server.go b/server/server.go index 601d8c14cf..6c267c18cb 100644 --- a/server/server.go +++ b/server/server.go @@ -825,6 +825,53 @@ func (j *jsonRPCHub) TraceBlock( return results, nil } +// IntermediateRoots executes a block (bad- or canon- or side-), and returns a list +// of intermediate roots: the stateroot after each transaction. +func (j *jsonRPCHub) IntermediateRoots( + block *types.Block, + tracer tracer.Tracer, +) ([]interface{}, error) { + if block.Number() == 0 { + return nil, errors.New("genesis block can't have transaction") + } + + parentHeader, ok := j.GetHeaderByHash(block.ParentHash()) + if !ok { + return nil, errors.New("parent header not found") + } + + blockCreator, err := j.GetConsensus().GetBlockCreator(block.Header) + if err != nil { + return nil, err + } + + transition, err := j.BeginTxn(parentHeader.StateRoot, block.Header, blockCreator) + if err != nil { + return nil, err + } + + transition.SetTracer(tracer) + + roots := make([]interface{}, len(block.Transactions)) + + for idx, tx := range block.Transactions { + tracer.Clear() + + if _, err := transition.Apply(tx); err != nil { + return roots, err + } + + _, h, err := transition.Commit() + if err != nil { + return roots, fmt.Errorf("failed to commit the state changes: %w", err) + } + + roots[idx] = h + } + + return roots, nil +} + // TraceTxn traces a transaction in the block, associated with the given hash func (j *jsonRPCHub) TraceTxn( block *types.Block,