diff --git a/core/state_processor.go b/core/state_processor.go index 623ed41789..089927b866 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -83,18 +83,9 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } } - if random.IsRunning(vmRunner) { - author, err := p.bc.Engine().Author(header) - if err != nil { - return nil, nil, 0, err - } - - err = random.RevealAndCommit(vmRunner, block.Randomness().Revealed, block.Randomness().Committed, author) - if err != nil { - return nil, nil, 0, err - } - // always true (EIP158) - statedb.IntermediateRoot(true) + err := ApplyBlockRandomnessTx(block, &vmRunner, statedb, p.bc) + if err != nil { + return nil, nil, 0, err } var ( @@ -212,3 +203,21 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, txFeeRecipien vmenv := vm.NewEVM(blockContext, vm.TxContext{}, statedb, config, cfg) return applyTransaction(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv, vmRunner, sysCtx) } + +func ApplyBlockRandomnessTx(block *types.Block, vmRunner *vm.EVMRunner, statedb *state.StateDB, bc *BlockChain) error { + if !random.IsRunning(*vmRunner) { + return nil + } + author, err := bc.Engine().Author(block.Header()) + if err != nil { + return err + } + + err = random.RevealAndCommit(*vmRunner, block.Randomness().Revealed, block.Randomness().Committed, author) + if err != nil { + return err + } + // always true (EIP158) + statedb.IntermediateRoot(true) + return nil +} diff --git a/eth/api_backend.go b/eth/api_backend.go index a51d983da2..a0aa15665f 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -477,8 +477,8 @@ func (b *EthAPIBackend) StartMining() error { return b.eth.StartMining() } -func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) { - return b.eth.stateAtBlock(block, reexec, base, checkLive, preferDisk) +func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool, commitRandomness bool) (*state.StateDB, error) { + return b.eth.celoStateAtBlock(block, reexec, base, checkLive, preferDisk, commitRandomness) } func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, vm.EVMRunner, *state.StateDB, error) { diff --git a/eth/celo_state_accessor.go b/eth/celo_state_accessor.go new file mode 100644 index 0000000000..d43083eb4f --- /dev/null +++ b/eth/celo_state_accessor.go @@ -0,0 +1,35 @@ +package eth + +import ( + "fmt" + + "github.com/celo-org/celo-blockchain/core" + "github.com/celo-org/celo-blockchain/core/state" + "github.com/celo-org/celo-blockchain/core/types" +) + +// Wraps `stateAtBlock` with the additional Celo-specific parameter `commitRandomness`. +// This parameter executes the random commitment at the start of the next block, +// since this is necessary for properly tracing transactions in the next block. +func (eth *Ethereum) celoStateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool, commitRandomness bool) (statedb *state.StateDB, err error) { + statedb, err = eth.stateAtBlock(block, reexec, base, checkLive, preferDisk) + if err != nil { + return nil, err + } + + if !commitRandomness { + return statedb, nil + } + // Fetch next block's random commitment + nextBlockNum := block.NumberU64() + 1 + nextBlock := eth.blockchain.GetBlockByNumber(nextBlockNum) + if nextBlock == nil { + return nil, fmt.Errorf("next block %d not found", nextBlockNum) + } + vmRunner := eth.blockchain.NewEVMRunner(nextBlock.Header(), statedb) + err = core.ApplyBlockRandomnessTx(nextBlock, &vmRunner, statedb, eth.blockchain) + if err != nil { + return nil, err + } + return statedb, nil +} diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 90ba9cbb67..863359982e 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -172,7 +172,8 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec } // Lookup the statedb of parent block from the live database, // otherwise regenerate it on the flight. - statedb, err := eth.stateAtBlock(parent, reexec, nil, true, false) + // Ensure that we get the state up to the random commitment in the current block + statedb, err := eth.celoStateAtBlock(parent, reexec, nil, true, false, true) if err != nil { return nil, vm.BlockContext{}, nil, nil, err } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index feb07eed8c..286d1e7dde 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -78,7 +78,8 @@ type Backend interface { // StateAtBlock returns the state corresponding to the stateroot of the block. // N.B: For executing transactions on block N, the required stateRoot is block N-1, // so this method should be called with the parent. - StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool) (*state.StateDB, error) + // On Celo, this should also include the random commitment of the current block. + StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive, preferDisk bool, commitRandomness bool) (*state.StateDB, error) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, vm.EVMRunner, *state.StateDB, error) NewEVMRunner(*types.Header, vm.StateDB) vm.EVMRunner } @@ -271,6 +272,23 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config if threads > blocks { threads = blocks } + + // Celo addition, required for correct block randomness values when tracing. + // This is an optimization that prevents needing to regenerate and process + // all preceding blocks for every single block when tracing on a full node; + // this is largely irrelevant when tracing on an archive node. + var baseStatedb *state.StateDB + if start.NumberU64() > 0 { + previousBlock, err := api.blockByNumber(ctx, rpc.BlockNumber(start.NumberU64()-1)) + if err != nil { + return nil, fmt.Errorf("failed to get block state prior to start") + } + baseStatedb, err = api.backend.StateAtBlock(ctx, previousBlock, reexec, nil, false, false, false) + if err != nil { + return nil, fmt.Errorf("failed to compute base statedb") + } + } + var ( pend = new(sync.WaitGroup) tasks = make(chan *blockTraceTask, threads) @@ -382,7 +400,12 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config } // 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, preferDisk) + // N.B. Celo does not pass in the previous statedb as a `base`, + // as this persists the previous version with the next block's random commitment. + // Instead, it passes in `baseStatedb`, defined above, which commits + // the random commitment made by `Process` in (eth.stateAtBlock), + // but NOT the next block's commitment made in eth.celoStateAtBlock. + statedb, err = api.backend.StateAtBlock(localctx, block, reexec, baseStatedb, false, preferDisk, true) if err != nil { failed = err break @@ -535,7 +558,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) + statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false, true) if err != nil { return nil, err } @@ -603,7 +626,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) + statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false, true) if err != nil { return nil, err } @@ -703,7 +726,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) + statedb, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false, true) if err != nil { return nil, err } @@ -852,7 +875,7 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * if err != nil { return nil, err } - sysStateDB, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false) + sysStateDB, err := api.backend.StateAtBlock(ctx, parent, reexec, nil, true, false, true) if err != nil { return nil, err } @@ -896,7 +919,9 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc if config != nil && config.Reexec != nil { reexec = *config.Reexec } - statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false) + // Since this is applied at the end of the requested block (often "latest"), + // do not commit the following block's randomness + statedb, err := api.backend.StateAtBlock(ctx, block, reexec, nil, true, false, false) if err != nil { return nil, err } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 86306ab7e2..da354bccce 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -148,7 +148,7 @@ 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, preferDisk bool) (*state.StateDB, error) { +func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool, commitRandomness bool) (*state.StateDB, error) { statedb, err := b.chain.StateAt(block.Root()) if err != nil { return nil, errStateNotFound diff --git a/les/api_backend.go b/les/api_backend.go index 4dbeda23c2..aaa6c3bae9 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -449,7 +449,7 @@ func (b *LesApiBackend) CurrentHeader() *types.Header { return b.eth.blockchain.CurrentHeader() } -func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) { +func (b *LesApiBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool, commitRandomness bool) (*state.StateDB, error) { return b.eth.stateAtBlock(ctx, block, reexec) }