forked from cerc-io/plugeth
ethclient/simulated: implement new sim backend (#28202)
This is a rewrite of the 'simulated backend', an implementation of the ethclient interfaces which is backed by a simulated blockchain. It was getting annoying to maintain the old version of the simulated backend feature because there was a lot of code duplication with the main client. The new version is built using parts that we already have: an in-memory geth node instance running in developer mode provides the chain, while the Go API is provided by ethclient. A backwards-compatibility wrapper is provided, but the simulated backend has also moved to a more sensible import path: github.com/ethereum/go-ethereum/ethclient/simulated --------- Co-authored-by: Felix Lange <fjl@twurst.com> Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
co-authored by
Felix Lange
Gary Rong
parent
9e018ce3a5
commit
2d08c99009
@@ -19,16 +19,17 @@ package catalyst
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/beacon/engine"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/eth"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/node"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
@@ -81,6 +82,11 @@ type SimulatedBeacon struct {
|
||||
lastBlockTime uint64
|
||||
}
|
||||
|
||||
// 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) {
|
||||
block := eth.BlockChain().CurrentBlock()
|
||||
current := engine.ForkchoiceStateV1{
|
||||
@@ -116,7 +122,9 @@ func (c *SimulatedBeacon) setFeeRecipient(feeRecipient common.Address) {
|
||||
// Start invokes the SimulatedBeacon life-cycle function in a goroutine.
|
||||
func (c *SimulatedBeacon) Start() error {
|
||||
if c.period == 0 {
|
||||
go c.loopOnDemand()
|
||||
// if period is set to 0, do not mine at all
|
||||
// this is used in the simulated backend where blocks
|
||||
// are explicitly mined via Commit, AdjustTime and Fork
|
||||
} else {
|
||||
go c.loop()
|
||||
}
|
||||
@@ -131,10 +139,9 @@ func (c *SimulatedBeacon) Stop() error {
|
||||
|
||||
// sealBlock initiates payload building for a new block and creates a new block
|
||||
// with the completed payload.
|
||||
func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
|
||||
tstamp := uint64(time.Now().Unix())
|
||||
if tstamp <= c.lastBlockTime {
|
||||
tstamp = c.lastBlockTime + 1
|
||||
func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal, timestamp uint64) error {
|
||||
if timestamp <= c.lastBlockTime {
|
||||
timestamp = c.lastBlockTime + 1
|
||||
}
|
||||
c.feeRecipientLock.Lock()
|
||||
feeRecipient := c.feeRecipient
|
||||
@@ -149,7 +156,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
|
||||
var random [32]byte
|
||||
rand.Read(random[:])
|
||||
fcResponse, err := c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, &engine.PayloadAttributes{
|
||||
Timestamp: tstamp,
|
||||
Timestamp: timestamp,
|
||||
SuggestedFeeRecipient: feeRecipient,
|
||||
Withdrawals: withdrawals,
|
||||
Random: random,
|
||||
@@ -183,6 +190,7 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
|
||||
return err
|
||||
}
|
||||
c.setCurrentState(payload.BlockHash, finalizedHash)
|
||||
|
||||
// Mark the block containing the payload as canonical
|
||||
if _, err = c.engineAPI.ForkchoiceUpdatedV2(c.curForkchoiceState, nil); err != nil {
|
||||
return err
|
||||
@@ -191,32 +199,6 @@ func (c *SimulatedBeacon) sealBlock(withdrawals []*types.Withdrawal) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loopOnDemand runs the block production loop for "on-demand" configuration (period = 0)
|
||||
func (c *SimulatedBeacon) loopOnDemand() {
|
||||
var (
|
||||
newTxs = make(chan core.NewTxsEvent)
|
||||
sub = c.eth.TxPool().SubscribeTransactions(newTxs, true)
|
||||
)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-c.shutdownCh:
|
||||
return
|
||||
case w := <-c.withdrawals.pending:
|
||||
withdrawals := append(c.withdrawals.gatherPending(9), w)
|
||||
if err := c.sealBlock(withdrawals); err != nil {
|
||||
log.Warn("Error performing sealing work", "err", err)
|
||||
}
|
||||
case <-newTxs:
|
||||
withdrawals := c.withdrawals.gatherPending(10)
|
||||
if err := c.sealBlock(withdrawals); err != nil {
|
||||
log.Warn("Error performing sealing work", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loop runs the block production loop for non-zero period configuration
|
||||
func (c *SimulatedBeacon) loop() {
|
||||
timer := time.NewTimer(0)
|
||||
@@ -226,7 +208,7 @@ func (c *SimulatedBeacon) loop() {
|
||||
return
|
||||
case <-timer.C:
|
||||
withdrawals := c.withdrawals.gatherPending(10)
|
||||
if err := c.sealBlock(withdrawals); err != nil {
|
||||
if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
|
||||
log.Warn("Error performing sealing work", "err", err)
|
||||
} else {
|
||||
timer.Reset(time.Second * time.Duration(c.period))
|
||||
@@ -235,8 +217,8 @@ func (c *SimulatedBeacon) loop() {
|
||||
}
|
||||
}
|
||||
|
||||
// finalizedBlockHash returns the block hash of the finalized block corresponding to the given number
|
||||
// or nil if doesn't exist in the chain.
|
||||
// finalizedBlockHash returns the block hash of the finalized block corresponding
|
||||
// to the given number or nil if doesn't exist in the chain.
|
||||
func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
|
||||
var finalizedNumber uint64
|
||||
if number%devEpochLength == 0 {
|
||||
@@ -244,7 +226,6 @@ func (c *SimulatedBeacon) finalizedBlockHash(number uint64) *common.Hash {
|
||||
} else {
|
||||
finalizedNumber = (number - 1) / devEpochLength * devEpochLength
|
||||
}
|
||||
|
||||
if finalizedBlock := c.eth.BlockChain().GetBlockByNumber(finalizedNumber); finalizedBlock != nil {
|
||||
fh := finalizedBlock.Hash()
|
||||
return &fh
|
||||
@@ -261,11 +242,60 @@ func (c *SimulatedBeacon) setCurrentState(headHash, finalizedHash common.Hash) {
|
||||
}
|
||||
}
|
||||
|
||||
// Commit seals a block on demand.
|
||||
func (c *SimulatedBeacon) Commit() common.Hash {
|
||||
withdrawals := c.withdrawals.gatherPending(10)
|
||||
if err := c.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
|
||||
log.Warn("Error performing sealing work", "err", err)
|
||||
}
|
||||
return c.eth.BlockChain().CurrentBlock().Hash()
|
||||
}
|
||||
|
||||
// Rollback un-sends previously added transactions.
|
||||
func (c *SimulatedBeacon) Rollback() {
|
||||
// Flush all transactions from the transaction pools
|
||||
maxUint256 := new(big.Int).Sub(new(big.Int).Lsh(common.Big1, 256), common.Big1)
|
||||
c.eth.TxPool().SetGasTip(maxUint256)
|
||||
// Set the gas tip back to accept new transactions
|
||||
// TODO (Marius van der Wijden): set gas tip to parameter passed by config
|
||||
c.eth.TxPool().SetGasTip(big.NewInt(params.GWei))
|
||||
}
|
||||
|
||||
// Fork sets the head to the provided hash.
|
||||
func (c *SimulatedBeacon) Fork(parentHash common.Hash) error {
|
||||
if len(c.eth.TxPool().Pending(false)) != 0 {
|
||||
return errors.New("pending block dirty")
|
||||
}
|
||||
parent := c.eth.BlockChain().GetBlockByHash(parentHash)
|
||||
if parent == nil {
|
||||
return errors.New("parent not found")
|
||||
}
|
||||
return c.eth.BlockChain().SetHead(parent.NumberU64())
|
||||
}
|
||||
|
||||
// AdjustTime creates a new block with an adjusted timestamp.
|
||||
func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
|
||||
if len(c.eth.TxPool().Pending(false)) != 0 {
|
||||
return errors.New("could not adjust time on non-empty block")
|
||||
}
|
||||
parent := c.eth.BlockChain().CurrentBlock()
|
||||
if parent == nil {
|
||||
return errors.New("parent not found")
|
||||
}
|
||||
withdrawals := c.withdrawals.gatherPending(10)
|
||||
return c.sealBlock(withdrawals, parent.Time+uint64(adjustment))
|
||||
}
|
||||
|
||||
func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
|
||||
api := &api{sim}
|
||||
if sim.period == 0 {
|
||||
// mine on demand if period is set to 0
|
||||
go api.loop()
|
||||
}
|
||||
stack.RegisterAPIs([]rpc.API{
|
||||
{
|
||||
Namespace: "dev",
|
||||
Service: &api{sim},
|
||||
Service: api,
|
||||
Version: "1.0",
|
||||
},
|
||||
})
|
||||
|
||||
@@ -18,19 +18,44 @@ package catalyst
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type api struct {
|
||||
simBeacon *SimulatedBeacon
|
||||
sim *SimulatedBeacon
|
||||
}
|
||||
|
||||
func (a *api) loop() {
|
||||
var (
|
||||
newTxs = make(chan core.NewTxsEvent)
|
||||
sub = a.sim.eth.TxPool().SubscribeTransactions(newTxs, true)
|
||||
)
|
||||
defer sub.Unsubscribe()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-a.sim.shutdownCh:
|
||||
return
|
||||
case w := <-a.sim.withdrawals.pending:
|
||||
withdrawals := append(a.sim.withdrawals.gatherPending(9), w)
|
||||
if err := a.sim.sealBlock(withdrawals, uint64(time.Now().Unix())); err != nil {
|
||||
log.Warn("Error performing sealing work", "err", err)
|
||||
}
|
||||
case <-newTxs:
|
||||
a.sim.Commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *api) AddWithdrawal(ctx context.Context, withdrawal *types.Withdrawal) error {
|
||||
return a.simBeacon.withdrawals.add(withdrawal)
|
||||
return a.sim.withdrawals.add(withdrawal)
|
||||
}
|
||||
|
||||
func (a *api) SetFeeRecipient(ctx context.Context, feeRecipient common.Address) {
|
||||
a.simBeacon.setFeeRecipient(feeRecipient)
|
||||
a.sim.setFeeRecipient(feeRecipient)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user