@ -20,6 +20,7 @@ import (
"crypto/rand"
"crypto/rand"
"crypto/sha256"
"crypto/sha256"
"errors"
"errors"
"fmt"
"math/big"
"math/big"
"sync"
"sync"
"time"
"time"
@ -30,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/params"
@ -41,36 +43,46 @@ const devEpochLength = 32
// withdrawalQueue implements a FIFO queue which holds withdrawals that are
// withdrawalQueue implements a FIFO queue which holds withdrawals that are
// pending inclusion.
// pending inclusion.
type withdrawalQueue struct {
type withdrawalQueue struct {
pending chan * types . Withdrawal
pending types . Withdrawals
mu sync . Mutex
feed event . Feed
subs event . SubscriptionScope
}
}
type newWithdrawalsEvent struct { Withdrawals types . Withdrawals }
// add queues a withdrawal for future inclusion.
// add queues a withdrawal for future inclusion.
func ( w * withdrawalQueue ) add ( withdrawal * types . Withdrawal ) error {
func ( w * withdrawalQueue ) add ( withdrawal * types . Withdrawal ) error {
select {
w . mu . Lock ( )
case w . pending <- withdrawal :
w . pending = append ( w . pending , withdrawal )
break
w . mu . Unlock ( )
default :
return errors . New ( "withdrawal queue full" )
w . feed . Send ( newWithdrawalsEvent { types . Withdrawals { withdrawal } } )
}
return nil
return nil
}
}
// gatherPending returns a number of queued withdrawals up to a maximum count.
// pop dequeues the specified number of withdrawals from the queue.
func ( w * withdrawalQueue ) gatherPending ( maxCount int ) [ ] * types . Withdrawal {
func ( w * withdrawalQueue ) pop ( count int ) types . Withdrawals {
withdrawals := [ ] * types . Withdrawal { }
w . mu . Lock ( )
for {
defer w . mu . Unlock ( )
select {
case withdrawal := <- w . pending :
count = min ( count , len ( w . pending ) )
withdrawals = append ( withdrawals , withdrawal )
popped := w . pending [ 0 : count ]
if len ( withdrawals ) == maxCount {
w . pending = w . pending [ count : ]
return withdrawals
}
return popped
default :
return withdrawals
}
}
}
// subscribe allows a listener to be updated when new withdrawals are added to
// the queue.
func ( w * withdrawalQueue ) subscribe ( ch chan <- newWithdrawalsEvent ) event . Subscription {
sub := w . feed . Subscribe ( ch )
return w . subs . Track ( sub )
}
}
// SimulatedBeacon drives an Ethereum instance as if it were a real beacon
// client. It can run in period mode where it mines a new block every period
// (seconds) or on every transaction via Commit, Fork and AdjustTime.
type SimulatedBeacon struct {
type SimulatedBeacon struct {
shutdownCh chan struct { }
shutdownCh chan struct { }
eth * eth . Ethereum
eth * eth . Ethereum
@ -86,10 +98,6 @@ type SimulatedBeacon struct {
}
}
// NewSimulatedBeacon constructs a new simulated beacon chain.
// NewSimulatedBeacon constructs a new simulated beacon chain.
// Period sets the period in which blocks should be produced.
//
// - If period is set to 0, a block is produced on every transaction.
// via Commit, Fork and AdjustTime.
func NewSimulatedBeacon ( period uint64 , eth * eth . Ethereum ) ( * SimulatedBeacon , error ) {
func NewSimulatedBeacon ( period uint64 , eth * eth . Ethereum ) ( * SimulatedBeacon , error ) {
block := eth . BlockChain ( ) . CurrentBlock ( )
block := eth . BlockChain ( ) . CurrentBlock ( )
current := engine . ForkchoiceStateV1 {
current := engine . ForkchoiceStateV1 {
@ -112,7 +120,6 @@ func NewSimulatedBeacon(period uint64, eth *eth.Ethereum) (*SimulatedBeacon, err
engineAPI : engineAPI ,
engineAPI : engineAPI ,
lastBlockTime : block . Time ,
lastBlockTime : block . Time ,
curForkchoiceState : current ,
curForkchoiceState : current ,
withdrawals : withdrawalQueue { make ( chan * types . Withdrawal , 20 ) } ,
} , nil
} , nil
}
}
@ -156,6 +163,16 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
c . setCurrentState ( header . Hash ( ) , * finalizedHash )
c . setCurrentState ( header . Hash ( ) , * finalizedHash )
}
}
// Because transaction insertion, block insertion, and block production will
// happen without any timing delay between them in simulator mode and the
// transaction pool will be running its internal reset operation on a
// background thread, flaky executions can happen. To avoid the racey
// behavior, the pool will be explicitly blocked on its reset before
// continuing to the block production below.
if err := c . eth . APIBackend . TxPool ( ) . Sync ( ) ; err != nil {
return fmt . Errorf ( "failed to sync txpool: %w" , err )
}
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 {
@ -164,13 +181,14 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp u
Withdrawals : withdrawals ,
Withdrawals : withdrawals ,
Random : random ,
Random : random ,
BeaconRoot : & common . Hash { } ,
BeaconRoot : & common . Hash { } ,
} , engine . PayloadV3 , true )
} , engine . PayloadV3 )
if err != nil {
if err != nil {
return err
return err
}
}
if fcResponse == engine . STATUS_SYNCING {
if fcResponse == engine . STATUS_SYNCING {
return errors . New ( "chain rewind prevented invocation of payload creation" )
return errors . New ( "chain rewind prevented invocation of payload creation" )
}
}
envelope , err := c . engineAPI . getPayload ( * fcResponse . PayloadID , true )
envelope , err := c . engineAPI . getPayload ( * fcResponse . PayloadID , true )
if err != nil {
if err != nil {
return err
return err
@ -223,8 +241,7 @@ func (c *SimulatedBeacon) loop() {
case <- c . shutdownCh :
case <- c . shutdownCh :
return
return
case <- timer . C :
case <- timer . C :
withdrawals := c . withdrawals . gatherPending ( 10 )
if err := c . sealBlock ( c . withdrawals . pop ( 10 ) , 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 . Second * time . Duration ( c . period ) )
@ -260,7 +277,7 @@ func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
// Commit seals a block on demand.
// Commit seals a block on demand.
func ( c * SimulatedBeacon ) Commit ( ) common . Hash {
func ( c * SimulatedBeacon ) Commit ( ) common . Hash {
withdrawals := c . withdrawals . gatherPending ( 10 )
withdrawals := c . withdrawals . pop ( 10 )
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 )
}
}
@ -301,16 +318,14 @@ func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
if parent == nil {
if parent == nil {
return errors . New ( "parent not found" )
return errors . New ( "parent not found" )
}
}
withdrawals := c . withdrawals . gatherPending ( 10 )
withdrawals := c . withdrawals . pop ( 10 )
return c . sealBlock ( withdrawals , parent . Time + uint64 ( adjustment / time . Second ) )
return c . sealBlock ( withdrawals , parent . Time + uint64 ( adjustment / time . Second ) )
}
}
// RegisterSimulatedBeaconAPIs registers the simulated beacon's API with the
// stack.
func RegisterSimulatedBeaconAPIs ( stack * node . Node , sim * SimulatedBeacon ) {
func RegisterSimulatedBeaconAPIs ( stack * node . Node , sim * SimulatedBeacon ) {
api := & api { sim }
api := newSimulatedBeaconAPI ( sim )
if sim . period == 0 {
// mine on demand if period is set to 0
go api . loop ( )
}
stack . RegisterAPIs ( [ ] rpc . API {
stack . RegisterAPIs ( [ ] rpc . API {
{
{
Namespace : "dev" ,
Namespace : "dev" ,