core: add basic chain history support in GenerateChain

This change improves GenerateChain to support internal chain history access (ChainReader)
for the consensus engine and EVM.

GenerateChain takes a `parent` block and the number of blocks to create. With my changes,
the consensus engine and EVM can now access blocks from `parent` up to the block currently
being generated. This is required to make the BLOCKHASH instruction work, and also needed
to create real clique chains. Clique uses the chain history to create its 'snapshot'.

I've also added some more accessors to BlockGen. These are helpful when creating
transactions:

- g.Signer returns a signer instance for the current block
- g.Difficulty returns the current block difficulty
- g.Gas returns the remaining gas amount
This commit is contained in:
Felix Lange 2023-10-27 15:58:43 +02:00
parent abe3fca1de
commit 359171f2b0
3 changed files with 137 additions and 68 deletions

View file

@ -84,7 +84,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
toaddr := common.Address{} toaddr := common.Address{}
data := make([]byte, nbytes) data := make([]byte, nbytes)
gas, _ := IntrinsicGas(data, nil, false, false, false, false) gas, _ := IntrinsicGas(data, nil, false, false, false, false)
signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), gen.header.Time) signer := gen.Signer()
gasPrice := big.NewInt(0) gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil { if gen.header.BaseFee != nil {
gasPrice = gen.header.BaseFee gasPrice = gen.header.BaseFee
@ -128,7 +128,7 @@ func genTxRing(naccounts int) func(int, *BlockGen) {
if gen.header.BaseFee != nil { if gen.header.BaseFee != nil {
gasPrice = gen.header.BaseFee gasPrice = gen.header.BaseFee
} }
signer := types.MakeSigner(gen.config, big.NewInt(int64(i)), gen.header.Time) signer := gen.Signer()
for { for {
gas -= params.TxGas gas -= params.TxGas
if gas < params.TxGas { if gas < params.TxGas {

View file

@ -38,8 +38,8 @@ import (
// See GenerateChain for a detailed explanation. // See GenerateChain for a detailed explanation.
type BlockGen struct { type BlockGen struct {
i int i int
cm *chainMaker
parent *types.Block parent *types.Block
chain []*types.Block
header *types.Header header *types.Header
statedb *state.StateDB statedb *state.StateDB
@ -49,7 +49,6 @@ type BlockGen struct {
uncles []*types.Header uncles []*types.Header
withdrawals []*types.Withdrawal withdrawals []*types.Withdrawal
config *params.ChainConfig
engine consensus.Engine engine consensus.Engine
} }
@ -88,13 +87,18 @@ func (b *BlockGen) SetPoS() {
b.header.Difficulty = new(big.Int) b.header.Difficulty = new(big.Int)
} }
// Difficulty returns the currently calculated difficulty of the block.
func (b *BlockGen) Difficulty() *big.Int {
return new(big.Int).Set(b.header.Difficulty)
}
// SetParentBeaconRoot sets the parent beacon root field of the generated // SetParentBeaconRoot sets the parent beacon root field of the generated
// block. // block.
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
b.header.ParentBeaconRoot = &root b.header.ParentBeaconRoot = &root
var ( var (
blockContext = NewEVMBlockContext(b.header, nil, &b.header.Coinbase) blockContext = NewEVMBlockContext(b.header, nil, &b.header.Coinbase)
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.config, vm.Config{}) vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
) )
ProcessBeaconBlockRoot(root, vmenv, b.statedb) ProcessBeaconBlockRoot(root, vmenv, b.statedb)
} }
@ -111,7 +115,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
b.SetCoinbase(common.Address{}) b.SetCoinbase(common.Address{})
} }
b.statedb.SetTxContext(tx.Hash(), len(b.txs)) b.statedb.SetTxContext(tx.Hash(), len(b.txs))
receipt, err := ApplyTransaction(b.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig) receipt, err := ApplyTransaction(b.cm.config, bc, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vmConfig)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -125,11 +129,11 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti
// AddTx adds a transaction to the generated block. If no coinbase has // AddTx adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address. // been set, the block's coinbase is set to the zero address.
// //
// AddTx panics if the transaction cannot be executed. In addition to // AddTx panics if the transaction cannot be executed. In addition to the protocol-imposed
// the protocol-imposed limitations (gas limit, etc.), there are some // limitations (gas limit, etc.), there are some further limitations on the content of
// further limitations on the content of transactions that can be // transactions that can be added. Notably, contract code relying on the BLOCKHASH
// added. Notably, contract code relying on the BLOCKHASH instruction // instruction will panic during execution if it attempts to access a block number outside
// will panic during execution. // of the range created by GenerateChain.
func (b *BlockGen) AddTx(tx *types.Transaction) { func (b *BlockGen) AddTx(tx *types.Transaction) {
b.addTx(nil, vm.Config{}, tx) b.addTx(nil, vm.Config{}, tx)
} }
@ -137,11 +141,10 @@ func (b *BlockGen) AddTx(tx *types.Transaction) {
// AddTxWithChain adds a transaction to the generated block. If no coinbase has // AddTxWithChain adds a transaction to the generated block. If no coinbase has
// been set, the block's coinbase is set to the zero address. // been set, the block's coinbase is set to the zero address.
// //
// AddTxWithChain panics if the transaction cannot be executed. In addition to // AddTxWithChain panics if the transaction cannot be executed. In addition to the
// the protocol-imposed limitations (gas limit, etc.), there are some // protocol-imposed limitations (gas limit, etc.), there are some further limitations on
// further limitations on the content of transactions that can be // the content of transactions that can be added. If contract code relies on the BLOCKHASH
// added. If contract code relies on the BLOCKHASH instruction, // instruction, the block in chain will be returned.
// the block in chain will be returned.
func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) { func (b *BlockGen) AddTxWithChain(bc *BlockChain, tx *types.Transaction) {
b.addTx(bc, vm.Config{}, tx) b.addTx(bc, vm.Config{}, tx)
} }
@ -158,8 +161,7 @@ func (b *BlockGen) GetBalance(addr common.Address) *big.Int {
return b.statedb.GetBalance(addr) return b.statedb.GetBalance(addr)
} }
// AddUncheckedTx forcefully adds a transaction to the block without any // AddUncheckedTx forcefully adds a transaction to the block without any validation.
// validation.
// //
// AddUncheckedTx will cause consensus failures when used during real // AddUncheckedTx will cause consensus failures when used during real
// chain processing. This is best used in conjunction with raw block insertion. // chain processing. This is best used in conjunction with raw block insertion.
@ -182,6 +184,16 @@ func (b *BlockGen) BaseFee() *big.Int {
return new(big.Int).Set(b.header.BaseFee) return new(big.Int).Set(b.header.BaseFee)
} }
// Gas returns the amount of gas left in the current block.
func (b *BlockGen) Gas() uint64 {
return b.header.GasLimit - b.header.GasUsed
}
// Signer returns a valid signer instance for the current block.
func (b *BlockGen) Signer() types.Signer {
return types.MakeSigner(b.cm.config, b.header.Number, b.header.Time)
}
// AddUncheckedReceipt forcefully adds a receipts to the block without a // AddUncheckedReceipt forcefully adds a receipts to the block without a
// backing transaction. // backing transaction.
// //
@ -207,20 +219,19 @@ func (b *BlockGen) AddUncle(h *types.Header) {
var parent *types.Header var parent *types.Header
for i := b.i - 1; i >= 0; i-- { for i := b.i - 1; i >= 0; i-- {
if b.chain[i].Hash() == h.ParentHash { if b.cm.chain[i].Hash() == h.ParentHash {
parent = b.chain[i].Header() parent = b.cm.chain[i].Header()
break break
} }
} }
chainreader := &fakeChainReader{config: b.config} h.Difficulty = b.engine.CalcDifficulty(b.cm, b.header.Time, parent)
h.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, parent)
// The gas limit and price should be derived from the parent // The gas limit and price should be derived from the parent
h.GasLimit = parent.GasLimit h.GasLimit = parent.GasLimit
if b.config.IsLondon(h.Number) { if b.cm.config.IsLondon(h.Number) {
h.BaseFee = eip1559.CalcBaseFee(b.config, parent) h.BaseFee = eip1559.CalcBaseFee(b.cm.config, parent)
if !b.config.IsLondon(parent.Number) { if !b.cm.config.IsLondon(parent.Number) {
parentGasLimit := parent.GasLimit * b.config.ElasticityMultiplier() parentGasLimit := parent.GasLimit * b.cm.config.ElasticityMultiplier()
h.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) h.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit)
} }
} }
@ -242,12 +253,12 @@ func (b *BlockGen) nextWithdrawalIndex() uint64 {
return b.withdrawals[len(b.withdrawals)-1].Index + 1 return b.withdrawals[len(b.withdrawals)-1].Index + 1
} }
for i := b.i - 1; i >= 0; i-- { for i := b.i - 1; i >= 0; i-- {
if wd := b.chain[i].Withdrawals(); len(wd) != 0 { if wd := b.cm.chain[i].Withdrawals(); len(wd) != 0 {
return wd[len(wd)-1].Index + 1 return wd[len(wd)-1].Index + 1
} }
if i == 0 { if i == 0 {
// Correctly set the index if no parent had withdrawals. // Correctly set the index if no parent had withdrawals.
if wd := b.parent.Withdrawals(); len(wd) != 0 { if wd := b.cm.bottom.Withdrawals(); len(wd) != 0 {
return wd[len(wd)-1].Index + 1 return wd[len(wd)-1].Index + 1
} }
} }
@ -263,9 +274,9 @@ func (b *BlockGen) PrevBlock(index int) *types.Block {
panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i)) panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i))
} }
if index == -1 { if index == -1 {
return b.parent return b.cm.bottom
} }
return b.chain[index] return b.cm.chain[index]
} }
// OffsetTime modifies the time instance of a block, implicitly changing its // OffsetTime modifies the time instance of a block, implicitly changing its
@ -273,11 +284,10 @@ func (b *BlockGen) PrevBlock(index int) *types.Block {
// tied to chain length directly. // tied to chain length directly.
func (b *BlockGen) OffsetTime(seconds int64) { func (b *BlockGen) OffsetTime(seconds int64) {
b.header.Time += uint64(seconds) b.header.Time += uint64(seconds)
if b.header.Time <= b.parent.Header().Time { if b.header.Time <= b.cm.bottom.Header().Time {
panic("block time out of range") panic("block time out of range")
} }
chainreader := &fakeChainReader{config: b.config} b.header.Difficulty = b.engine.CalcDifficulty(b.cm, b.header.Time, b.parent.Header())
b.header.Difficulty = b.engine.CalcDifficulty(chainreader, b.header.Time, b.parent.Header())
} }
// GenerateChain creates a chain of n blocks. The first block's // GenerateChain creates a chain of n blocks. The first block's
@ -296,11 +306,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
if config == nil { if config == nil {
config = params.TestChainConfig config = params.TestChainConfig
} }
blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n) cm := newChainMaker(parent, config)
chainreader := &fakeChainReader{config: config}
genblock := func(i int, parent *types.Block, triedb *trie.Database, statedb *state.StateDB) (*types.Block, types.Receipts) { genblock := func(i int, parent *types.Block, triedb *trie.Database, statedb *state.StateDB) (*types.Block, types.Receipts) {
b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine} b := &BlockGen{i: i, cm: cm, parent: parent, statedb: statedb, engine: engine}
b.header = makeHeader(chainreader, parent, statedb, b.engine) b.header = cm.makeHeader(parent, statedb, b.engine)
// Set the difficulty for clique block. The chain maker doesn't have access // Set the difficulty for clique block. The chain maker doesn't have access
// to a chain, so the difficulty will be left unset (nil). Set it here to the // to a chain, so the difficulty will be left unset (nil). Set it here to the
@ -331,7 +341,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
gen(i, b) gen(i, b)
} }
if b.engine != nil { if b.engine != nil {
block, err := b.engine.FinalizeAndAssemble(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals) block, err := b.engine.FinalizeAndAssemble(cm, b.header, statedb, b.txs, b.uncles, b.receipts, b.withdrawals)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -348,6 +358,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
} }
return nil, nil return nil, nil
} }
// Forcibly use hash-based state scheme for retaining all nodes in disk. // Forcibly use hash-based state scheme for retaining all nodes in disk.
triedb := trie.NewDatabase(db, trie.HashDefaults) triedb := trie.NewDatabase(db, trie.HashDefaults)
defer triedb.Close() defer triedb.Close()
@ -358,11 +369,10 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
panic(err) panic(err)
} }
block, receipt := genblock(i, parent, triedb, statedb) block, receipt := genblock(i, parent, triedb, statedb)
blocks[i] = block cm.add(block, receipt)
receipts[i] = receipt
parent = block parent = block
} }
return blocks, receipts return cm.chain, cm.receipts
} }
// GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize // GenerateChainWithGenesis is a wrapper of GenerateChain which will initialize
@ -380,35 +390,32 @@ func GenerateChainWithGenesis(genesis *Genesis, engine consensus.Engine, n int,
return db, blocks, receipts return db, blocks, receipts
} }
func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { func (cm *chainMaker) makeHeader(parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
var time uint64 var time uint64
if parent.Time() == 0 { if parent.Time() == 0 {
time = 10 time = 10
} else { } else {
time = parent.Time() + 10 // block time is fixed at 10 seconds time = parent.Time() + 10 // block time is fixed at 10 seconds
} }
header := &types.Header{ header := &types.Header{
Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), Root: state.IntermediateRoot(cm.config.IsEIP158(parent.Number())),
ParentHash: parent.Hash(), ParentHash: parent.Hash(),
Coinbase: parent.Coinbase(), Coinbase: parent.Coinbase(),
Difficulty: engine.CalcDifficulty(chain, time, &types.Header{ Difficulty: engine.CalcDifficulty(cm, time, parent.Header()),
Number: parent.Number(),
Time: time - 10,
Difficulty: parent.Difficulty(),
UncleHash: parent.UncleHash(),
}),
GasLimit: parent.GasLimit(), GasLimit: parent.GasLimit(),
Number: new(big.Int).Add(parent.Number(), common.Big1), Number: new(big.Int).Add(parent.Number(), common.Big1),
Time: time, Time: time,
} }
if chain.Config().IsLondon(header.Number) {
header.BaseFee = eip1559.CalcBaseFee(chain.Config(), parent.Header()) if cm.config.IsLondon(header.Number) {
if !chain.Config().IsLondon(parent.Number()) { header.BaseFee = eip1559.CalcBaseFee(cm.config, parent.Header())
parentGasLimit := parent.GasLimit() * chain.Config().ElasticityMultiplier() if !cm.config.IsLondon(parent.Number()) {
parentGasLimit := parent.GasLimit() * cm.config.ElasticityMultiplier()
header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit) header.GasLimit = CalcGasLimit(parentGasLimit, parentGasLimit)
} }
} }
if chain.Config().IsCancun(header.Number, header.Time) { if cm.config.IsCancun(header.Number, header.Time) {
var ( var (
parentExcessBlobGas uint64 parentExcessBlobGas uint64
parentBlobGasUsed uint64 parentBlobGasUsed uint64
@ -461,18 +468,79 @@ func makeBlockChainWithGenesis(genesis *Genesis, n int, engine consensus.Engine,
return db, blocks return db, blocks
} }
type fakeChainReader struct { // chainMaker contains the state of chain generation.
type chainMaker struct {
bottom *types.Block
config *params.ChainConfig config *params.ChainConfig
chain []*types.Block
chainByHash map[common.Hash]*types.Block
receipts []types.Receipts
} }
func newChainMaker(bottom *types.Block, config *params.ChainConfig) *chainMaker {
return &chainMaker{
bottom: bottom,
config: config,
chainByHash: make(map[common.Hash]*types.Block),
}
}
func (cm *chainMaker) add(b *types.Block, r []*types.Receipt) {
cm.chain = append(cm.chain, b)
cm.chainByHash[b.Hash()] = b
cm.receipts = append(cm.receipts, r)
}
func (cm *chainMaker) blockByNumber(number uint64) *types.Block {
if number == cm.bottom.NumberU64() {
return cm.bottom
}
cur := cm.CurrentHeader().Number.Uint64()
lowest := cm.bottom.NumberU64() + 1
if number < lowest || number > cur {
return nil
}
return cm.chain[number-lowest]
}
// ChainReader implementation
// Config returns the chain configuration. // Config returns the chain configuration.
func (cr *fakeChainReader) Config() *params.ChainConfig { func (cm *chainMaker) Config() *params.ChainConfig {
return cr.config return cm.config
} }
func (cr *fakeChainReader) CurrentHeader() *types.Header { return nil } func (cm *chainMaker) CurrentHeader() *types.Header {
func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header { return nil } if len(cm.chain) == 0 {
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil } return cm.bottom.Header()
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil } }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } return cm.chain[len(cm.chain)-1].Header()
func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil } }
func (cm *chainMaker) GetHeaderByNumber(number uint64) *types.Header {
b := cm.blockByNumber(number)
if b == nil {
return nil
}
return b.Header()
}
func (cm *chainMaker) GetHeaderByHash(hash common.Hash) *types.Header {
b := cm.chainByHash[hash]
if b == nil {
return nil
}
return b.Header()
}
func (cm *chainMaker) GetHeader(hash common.Hash, number uint64) *types.Header {
return cm.GetHeaderByNumber(number)
}
func (cm *chainMaker) GetBlock(hash common.Hash, number uint64) *types.Block {
return cm.blockByNumber(number)
}
func (cm *chainMaker) GetTd(hash common.Hash, number uint64) *big.Int {
return nil // not supported
}

View file

@ -359,7 +359,8 @@ func TestStateProcessorErrors(t *testing.T) {
func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block { func GenerateBadBlock(parent *types.Block, engine consensus.Engine, txs types.Transactions, config *params.ChainConfig) *types.Block {
difficulty := big.NewInt(0) difficulty := big.NewInt(0)
if !config.TerminalTotalDifficultyPassed { if !config.TerminalTotalDifficultyPassed {
difficulty = engine.CalcDifficulty(&fakeChainReader{config}, parent.Time()+10, &types.Header{ fakeChainReader := newChainMaker(nil, config)
difficulty = engine.CalcDifficulty(fakeChainReader, parent.Time()+10, &types.Header{
Number: parent.Number(), Number: parent.Number(),
Time: parent.Time(), Time: parent.Time(),
Difficulty: parent.Difficulty(), Difficulty: parent.Difficulty(),