Skip to content

Commit

Permalink
Fix tracing for transactions using on-chain randomness in latest block (
Browse files Browse the repository at this point in the history
#2186)

* First pass implementing fix

* Factor out ApplyBlockRandomnessTx

* Fix lint

* Nits: fix traceChain, add comment, remove TODOs

* Separate out celoStateAtBlock into celo-specific file

* Rename afterNextRandomCommit -> commitRandomness

* Fix bug in traceChain

* Fix typo
  • Loading branch information
Eela Nagaraj authored Sep 19, 2023
1 parent 0ed594e commit 87d69dc
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 24 deletions.
33 changes: 21 additions & 12 deletions core/state_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions eth/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
35 changes: 35 additions & 0 deletions eth/celo_state_accessor.go
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 2 additions & 1 deletion eth/state_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
39 changes: 32 additions & 7 deletions eth/tracers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down
2 changes: 1 addition & 1 deletion eth/tracers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion les/api_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down

0 comments on commit 87d69dc

Please sign in to comment.