cmd, eth: introduce BlockPeriod type for flexible beacon chain simulation

This commit is contained in:
Yahya 2024-04-09 20:21:29 -07:00
parent 34aac1d756
commit 45b5de3edc
No known key found for this signature in database
GPG key ID: A24E95684F0E4F49
4 changed files with 52 additions and 25 deletions

View file

@ -218,7 +218,11 @@ func makeFullNode(ctx *cli.Context) (*node.Node, ethapi.Backend) {
if ctx.IsSet(utils.DeveloperFlag.Name) { if ctx.IsSet(utils.DeveloperFlag.Name) {
// Start dev mode. // Start dev mode.
simBeacon, err := catalyst.NewSimulatedBeacon(ctx.Uint64(utils.DeveloperPeriodFlag.Name), eth) p, err := catalyst.ParseBlockPeriod(ctx.String(utils.DeveloperFlag.Name))
if err != nil {
utils.Fatalf("failed to parse developer mode block interval: %v", err)
}
simBeacon, err := catalyst.NewSimulatedBeacon(p, eth)
if err != nil { if err != nil {
utils.Fatalf("failed to register dev mode catalyst service: %v", err) utils.Fatalf("failed to register dev mode catalyst service: %v", err)
} }

View file

@ -36,7 +36,25 @@ import (
"github.com/ethereum/go-ethereum/rpc" "github.com/ethereum/go-ethereum/rpc"
) )
const devEpochLength = 32 const (
devEpochLength = 32
// ManualPeriod is a special value for the block interval that disables automatic block production, and
// requires manual invocation of the Commit, Fork and AdjustTime methods.
ManualPeriod = BlockPeriod(0)
)
type BlockPeriod time.Duration
func ParseBlockPeriod(period string) (BlockPeriod, error) {
if period == "0" {
return ManualPeriod, nil
}
d, err := time.ParseDuration(period)
if err != nil {
return 0, err
}
return BlockPeriod(d), nil
}
// withdrawalQueue implements a FIFO queue which holds withdrawals that are // withdrawalQueue implements a FIFO queue which holds withdrawals that are
// pending inclusion. // pending inclusion.
@ -74,23 +92,26 @@ func (w *withdrawalQueue) gatherPending(maxCount int) []*types.Withdrawal {
type SimulatedBeacon struct { type SimulatedBeacon struct {
shutdownCh chan struct{} shutdownCh chan struct{}
eth *eth.Ethereum eth *eth.Ethereum
period uint64 period BlockPeriod
withdrawals withdrawalQueue withdrawals withdrawalQueue
feeRecipient common.Address feeRecipient common.Address
feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient feeRecipientLock sync.Mutex // lock gates concurrent access to the feeRecipient
engineAPI *ConsensusAPI engineAPI *ConsensusAPI
curForkchoiceState engine.ForkchoiceStateV1 curForkChoiceState engine.ForkchoiceStateV1
lastBlockTime uint64 lastBlockTime uint64
} }
// NewSimulatedBeacon constructs a new simulated beacon chain. // NewSimulatedBeacon constructs a new simulated beacon chain.
// Period sets the period in which blocks should be produced. // Period sets the period in which blocks should be produced.
// // Args:
// - If period is set to 0, a block is produced on every transaction. // - period: the block production period, use ManualPeriod to disable automatic block production and require manual invocation of Commit, Fork and AdjustTime.
// via Commit, Fork and AdjustTime. // - eth: the Ethereum service to which the simulated beacon should be attached.
func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, error) { // Returns:
// - a new simulated beacon chain.
// - an error if the beacon could not be created.
func NewSimulatedBeacon(period BlockPeriod, eth *eth.Ethereum) (*SimulatedBeacon, error) {
block := eth.BlockChain().CurrentBlock() block := eth.BlockChain().CurrentBlock()
current := engine.ForkchoiceStateV1{ current := engine.ForkchoiceStateV1{
HeadBlockHash: block.Hash(), HeadBlockHash: block.Hash(),
@ -99,7 +120,7 @@ func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, err
} }
engineAPI := newConsensusAPIWithoutHeartbeat(eth) engineAPI := newConsensusAPIWithoutHeartbeat(eth)
// if genesis block, send forkchoiceUpdated to trigger transition to PoS // if genesis block, send forkChoiceUpdated to trigger transition to PoS
if block.Number.Sign() == 0 { if block.Number.Sign() == 0 {
if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil { if _, err := engineAPI.ForkchoiceUpdatedV2(current, nil); err != nil {
return nil, err return nil, err
@ -111,7 +132,7 @@ func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, err
shutdownCh: make(chan struct{}), shutdownCh: make(chan struct{}),
engineAPI: engineAPI, engineAPI: engineAPI,
lastBlockTime: block.Time, lastBlockTime: block.Time,
curForkchoiceState: current, curForkChoiceState: current,
withdrawals: withdrawalQueue{make(chan *types.Withdrawal, 20)}, withdrawals: withdrawalQueue{make(chan *types.Withdrawal, 20)},
}, nil }, nil
} }
@ -124,7 +145,7 @@ func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) {
// Start invokes the SimulatedBeacon life-cycle function in a goroutine. // Start invokes the SimulatedBeacon life-cycle function in a goroutine.
func (c *SimulatedBeacon) Start() error { func (c *SimulatedBeacon) Start() error {
if c.period == 0 { if c.period == ManualPeriod {
// if period is set to 0, do not mine at all // if period is set to 0, do not mine at all
// this is used in the simulated backend where blocks // this is used in the simulated backend where blocks
// are explicitly mined via Commit, AdjustTime and Fork // are explicitly mined via Commit, AdjustTime and Fork
@ -151,14 +172,14 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
c.feeRecipientLock.Unlock() c.feeRecipientLock.Unlock()
// Reset to CurrentBlock in case of the chain was rewound // Reset to CurrentBlock in case of the chain was rewound
if header := c.eth.BlockChain().CurrentBlock(); c.curForkchoiceState.HeadBlockHash != header.Hash() { if header := c.eth.BlockChain().CurrentBlock(); c.curForkChoiceState.HeadBlockHash != header.Hash() {
finalizedHash := c.finalizedBlockHash(header.Number.Uint64()) finalizedHash := c.finalizedBlockHash(header.Number.Uint64())
c.setCurrentState(header.Hash(), *finalizedHash) c.setCurrentState(header.Hash(), *finalizedHash)
} }
var random [32]byte var random [32]byte
rand.Read(random[:]) rand.Read(random[:])
fcResponse, err := c.engineAPI.forkchoiceUpdated(c.curForkchoiceState, &engine.PayloadAttributes{ fcResponse, err := c.engineAPI.forkchoiceUpdated(c.curForkChoiceState, &engine.PayloadAttributes{
Timestamp: timestamp, Timestamp: timestamp,
SuggestedFeeRecipient: feeRecipient, SuggestedFeeRecipient: feeRecipient,
Withdrawals: withdrawals, Withdrawals: withdrawals,
@ -208,7 +229,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
c.setCurrentState(payload.BlockHash, finalizedHash) c.setCurrentState(payload.BlockHash, finalizedHash)
// Mark the block containing the payload as canonical // Mark the block containing the payload as canonical
if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil { if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkChoiceState, nil); err != nil {
return err return err
} }
c.lastBlockTime = payload.Timestamp c.lastBlockTime = payload.Timestamp
@ -227,7 +248,7 @@ func (c *SimulatedBeacon) loop() {
if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil { if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
log.Warn("Error performing sealing work", "err", err) log.Warn("Error performing sealing work", "err", err)
} else { } else {
timer.Reset(time.Second * time.Duration(c.period)) timer.Reset(time.Duration(c.period))
} }
} }
} }
@ -249,9 +270,9 @@ func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
return nil return nil
} }
// setCurrentState sets the current forkchoice state // setCurrentState sets the current fork choice state.
func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) { func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
c.curForkchoiceState = engine.ForkchoiceStateV1{ c.curForkChoiceState = engine.ForkchoiceStateV1{
HeadBlockHash: headHash, HeadBlockHash: headHash,
SafeBlockHash: headHash, SafeBlockHash: headHash,
FinalizedBlockHash: finalizedHash, FinalizedBlockHash: finalizedHash,
@ -304,7 +325,7 @@ func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) { func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
api := &api{sim} api := &api{sim}
if sim.period == 0 { if sim.period == ManualPeriod {
// mine on demand if period is set to 0 // mine on demand if period is set to 0
go api.loop() go api.loop()
} }

View file

@ -55,7 +55,7 @@ func startSimulatedBeaconEthService(t *testing.T, genesis *core.Genesis) (*node.
t.Fatal("can't create eth service:", err) t.Fatal("can't create eth service:", err)
} }
simBeacon, err := NewSimulatedBeacon(1, ethservice) simBeacon, err := NewSimulatedBeacon(BlockPeriod(time.Second), ethservice)
if err != nil { if err != nil {
t.Fatal("can't create simulated beacon:", err) t.Fatal("can't create simulated beacon:", err)
} }

View file

@ -96,7 +96,7 @@ func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config,
if err != nil { if err != nil {
panic(err) // this should never happen panic(err) // this should never happen
} }
sim, err := newWithNode(stack, &ethConf, 0) sim, err := newWithNode(stack, &ethConf, catalyst.ManualPeriod)
if err != nil { if err != nil {
panic(err) // this should never happen panic(err) // this should never happen
} }
@ -105,17 +105,19 @@ func NewBackend(alloc types.GenesisAlloc, options ...func(nodeConf *node.Config,
// newWithNode sets up a simulated backend on an existing node. The provided node // newWithNode sets up a simulated backend on an existing node. The provided node
// must not be started and will be started by this method. // must not be started and will be started by this method.
func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backend, error) { func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod catalyst.BlockPeriod) (*Backend, error) {
backend, err := eth.New(stack, conf) backend, err := eth.New(stack, conf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Register the filter system // Register the filter system
filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{}) filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
stack.RegisterAPIs([]rpc.API{{ stack.RegisterAPIs([]rpc.API{
{
Namespace: "eth", Namespace: "eth",
Service: filters.NewFilterAPI(filterSystem, false), Service: filters.NewFilterAPI(filterSystem, false),
}}) },
})
// Start the node // Start the node
if err := stack.Start(); err != nil { if err := stack.Start(); err != nil {
return nil, err return nil, err