go-ethereum/miner/fake_miner.go
Manav Darji adad83d6ac
consensus/bor: add span store abstraction for stateless verification (#1472)
Add a new abstraction called span store in bor consensus. This makes the header verification stateless as instead of fetching validator set (of a span) from genesis contract, we rely on span from heimdall itself for validator set. This doesn't change any assumptions for consensus as the data in genesis contracts is written by fetching span details from heimdall in the first place. 

This helps us with multiple things such as header verification for snap sync, validating producer set against header's extra data, and using heimdall as a source of truth instead of making a contract call to genesis contract which generally leads to issues when importing side chains (as we need to use the side chain state and not canonical). 

Span store is a minimal cache which maintains last 10 spans in memory. It provide simple read methods via span id and block number so that we can use it in consensus.
2025-05-07 21:34:12 +05:30

248 lines
6.9 KiB
Go

package miner
import (
"errors"
"math/big"
"testing"
"github.com/golang/mock/gomock"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/bor"
"github.com/ethereum/go-ethereum/consensus/bor/api"
"github.com/ethereum/go-ethereum/consensus/bor/heimdall/span"
"github.com/ethereum/go-ethereum/consensus/bor/valset"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/ethdb/memorydb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/tests/bor/mocks"
"github.com/ethereum/go-ethereum/triedb"
)
type DefaultBorMiner struct {
Miner *Miner
Mux *event.TypeMux //nolint:staticcheck
Cleanup func(skipMiner bool)
Ctrl *gomock.Controller
EthAPIMock api.Caller
HeimdallClientMock bor.IHeimdallClient
ContractMock bor.GenesisContract
}
func NewBorDefaultMiner(t *testing.T) *DefaultBorMiner {
t.Helper()
ctrl := gomock.NewController(t)
ethAPI := api.NewMockCaller(ctrl)
ethAPI.EXPECT().Call(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes()
// Mock span 0 for heimdall
span0 := createMockSpanForTest(common.Address{0x1}, "1337")
spanner := bor.NewMockSpanner(ctrl)
spanner.EXPECT().GetCurrentValidatorsByHash(gomock.Any(), gomock.Any(), gomock.Any()).Return(span0.ValidatorSet.Validators, nil).AnyTimes()
heimdallClient := mocks.NewMockIHeimdallClient(ctrl)
heimdallClient.EXPECT().Span(gomock.Any(), uint64(0)).Return(&span0, nil).AnyTimes()
heimdallClient.EXPECT().Close().Times(1)
genesisContracts := bor.NewMockGenesisContract(ctrl)
miner, mux, cleanup := createBorMiner(t, ethAPI, spanner, heimdallClient, genesisContracts)
return &DefaultBorMiner{
Miner: miner,
Mux: mux,
Cleanup: cleanup,
Ctrl: ctrl,
EthAPIMock: ethAPI,
HeimdallClientMock: heimdallClient,
ContractMock: genesisContracts,
}
}
// //nolint:staticcheck
func createBorMiner(t *testing.T, ethAPIMock api.Caller, spanner bor.Spanner, heimdallClientMock bor.IHeimdallClient, contractMock bor.GenesisContract) (*Miner, *event.TypeMux, func(skipMiner bool)) {
t.Helper()
// Create Ethash config
chainDB, genspec, chainConfig := NewDBForFakes(t)
engine := NewFakeBor(t, chainDB, chainConfig, ethAPIMock, spanner, heimdallClientMock, contractMock)
// Create Ethereum backend
bc, err := core.NewBlockChain(chainDB, nil, genspec, nil, engine, vm.Config{}, nil, nil, nil)
if err != nil {
t.Fatalf("can't create new chain %v", err)
}
statedb, _ := state.New(common.Hash{}, state.NewDatabase(triedb.NewDatabase(rawdb.NewMemoryDatabase(), nil), nil))
blockchain := &testBlockChainBor{chainConfig, statedb, 10000000, new(event.Feed)}
pool := legacypool.New(testTxPoolConfigBor, blockchain)
txpool, _ := txpool.New(testTxPoolConfigBor.PriceLimit, blockchain, []txpool.SubPool{pool})
backend := NewMockBackendBor(bc, txpool)
// Create event Mux
mux := new(event.TypeMux)
config := Config{
Etherbase: common.HexToAddress("123456789"),
}
// Create Miner
miner := New(backend, &config, chainConfig, mux, engine, nil)
cleanup := func(skipMiner bool) {
bc.Stop()
engine.Close()
if !skipMiner {
miner.Close()
}
}
return miner, mux, cleanup
}
type TensingObject interface {
Helper()
Fatalf(format string, args ...any)
}
func NewDBForFakes(t TensingObject) (ethdb.Database, *core.Genesis, *params.ChainConfig) {
t.Helper()
memdb := memorydb.New()
chainDB := rawdb.NewDatabase(memdb)
addr := common.HexToAddress("12345")
genesis := core.DeveloperGenesisBlock(11_500_000, &addr)
chainConfig, _, err := core.SetupGenesisBlock(chainDB, triedb.NewDatabase(chainDB, triedb.HashDefaults), genesis)
if err != nil {
t.Fatalf("can't create new chain config: %v", err)
}
chainConfig.Bor.Period = map[string]uint64{
"0": 1,
}
chainConfig.Bor.Sprint = map[string]uint64{
"0": 64,
}
return chainDB, genesis, chainConfig
}
func NewFakeBor(t TensingObject, chainDB ethdb.Database, chainConfig *params.ChainConfig, ethAPIMock api.Caller, spanner bor.Spanner, heimdallClientMock bor.IHeimdallClient, contractMock bor.GenesisContract) consensus.Engine {
t.Helper()
if chainConfig.Bor == nil {
chainConfig.Bor = params.BorUnittestChainConfig.Bor
}
return bor.New(chainConfig, chainDB, ethAPIMock, spanner, heimdallClientMock, contractMock, false)
}
func createMockSpanForTest(address common.Address, chainId string) span.HeimdallSpan {
// Mock span 0 for heimdall calls
validator := valset.Validator{
ID: 0,
Address: address,
VotingPower: 100,
ProposerPriority: 0,
}
validatorSet := valset.ValidatorSet{
Validators: []*valset.Validator{&validator},
Proposer: &validator,
}
span0 := span.HeimdallSpan{
Span: span.Span{
ID: 0,
StartBlock: 0,
EndBlock: 255,
},
ValidatorSet: validatorSet,
SelectedProducers: []valset.Validator{validator},
ChainID: chainId,
}
return span0
}
var (
// Test chain configurations
testTxPoolConfigBor legacypool.Config
)
// TODO - Arpit, Duplicate Functions
type mockBackendBor struct {
bc *core.BlockChain
txPool *txpool.TxPool
}
func NewMockBackendBor(bc *core.BlockChain, txPool *txpool.TxPool) *mockBackendBor {
return &mockBackendBor{
bc: bc,
txPool: txPool,
}
}
func (m *mockBackendBor) BlockChain() *core.BlockChain {
return m.bc
}
// PeerCount implements Backend.
func (*mockBackendBor) PeerCount() int {
panic("unimplemented")
}
func (m *mockBackendBor) TxPool() *txpool.TxPool {
return m.txPool
}
func (m *mockBackendBor) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (statedb *state.StateDB, err error) {
return nil, errors.New("not supported")
}
// TODO - Arpit, Duplicate Functions
type testBlockChainBor struct {
config *params.ChainConfig
statedb *state.StateDB
gasLimit uint64
chainHeadFeed *event.Feed
}
func (bc *testBlockChainBor) Config() *params.ChainConfig {
return bc.config
}
func (bc *testBlockChainBor) CurrentBlock() *types.Header {
return &types.Header{
Number: new(big.Int),
GasLimit: bc.gasLimit,
}
}
func (bc *testBlockChainBor) GetBlock(hash common.Hash, number uint64) *types.Block {
return types.NewBlock(bc.CurrentBlock(), nil, nil, nil)
}
func (bc *testBlockChainBor) StateAt(common.Hash) (*state.StateDB, error) {
return bc.statedb, nil
}
func (bc *testBlockChainBor) SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription {
return bc.chainHeadFeed.Subscribe(ch)
}