Engine api (#7)

This commit is contained in:
Moon Shiesty 2023-10-10 15:27:53 -05:00 committed by MoonShiesty
parent 0e4999d59f
commit 7285ddd28b
8 changed files with 157 additions and 2 deletions

View file

@ -21,6 +21,10 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
// <specular modification>
Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"`
NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"`
// <specular modification/>
} }
var enc PayloadAttributes var enc PayloadAttributes
enc.Timestamp = hexutil.Uint64(p.Timestamp) enc.Timestamp = hexutil.Uint64(p.Timestamp)
@ -28,6 +32,15 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals enc.Withdrawals = p.Withdrawals
enc.BeaconRoot = p.BeaconRoot enc.BeaconRoot = p.BeaconRoot
// <specular modification>
if p.Transactions != nil {
enc.Transactions = make([]hexutil.Bytes, len(p.Transactions))
for k, v := range p.Transactions {
enc.Transactions[k] = v
}
}
enc.NoTxPool = p.NoTxPool
// <specular modification/>
return json.Marshal(&enc) return json.Marshal(&enc)
} }
@ -39,6 +52,10 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
// <specular modification>
Transactions []hexutil.Bytes `json:"transactions,omitempty" gencodec:"optional"`
NoTxPool *bool `json:"noTxPool,omitempty" gencodec:"optional"`
// <specular modification/>
} }
var dec PayloadAttributes var dec PayloadAttributes
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -62,5 +79,16 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.BeaconRoot != nil { if dec.BeaconRoot != nil {
p.BeaconRoot = dec.BeaconRoot p.BeaconRoot = dec.BeaconRoot
} }
// <specular modification>
if dec.Transactions != nil {
p.Transactions = make([][]byte, len(dec.Transactions))
for k, v := range dec.Transactions {
p.Transactions[k] = v
}
}
if dec.NoTxPool != nil {
p.NoTxPool = *dec.NoTxPool
}
// <specular modification/>
return nil return nil
} }

View file

@ -36,11 +36,21 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"` SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"` Withdrawals []*types.Withdrawal `json:"withdrawals"`
BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"` BeaconRoot *common.Hash `json:"parentBeaconBlockRoot"`
// <specular modification>
// Transactions is a field for L2s: the transactions list is forced into the block
Transactions [][]byte `json:"transactions,omitempty" gencodec:"optional"`
// NoTxPool is a field for L2s: if true, the no transactions are taken out of the tx-pool,
// only transactions from the above Transactions list will be included.
NoTxPool bool `json:"noTxPool,omitempty" gencodec:"optional"`
// <specular modification//>
} }
// JSON type overrides for PayloadAttributes. // JSON type overrides for PayloadAttributes.
type payloadAttributesMarshaling struct { type payloadAttributesMarshaling struct {
Timestamp hexutil.Uint64 Timestamp hexutil.Uint64
// <specular modification>
Transactions []hexutil.Bytes
// <specular modification/>
} }
//go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go //go:generate go run github.com/fjl/gencodec -type ExecutableData -field-override executableDataMarshaling -out gen_ed.go

View file

@ -144,6 +144,9 @@ var (
utils.GpoPercentileFlag, utils.GpoPercentileFlag,
utils.GpoMaxGasPriceFlag, utils.GpoMaxGasPriceFlag,
utils.GpoIgnoreGasPriceFlag, utils.GpoIgnoreGasPriceFlag,
// <specular modification>
utils.EnableL2EngineApiFlag,
// <specular modification/>
configFileFlag, configFileFlag,
}, utils.NetworkFlags, utils.DatabasePathFlags) }, utils.NetworkFlags, utils.DatabasePathFlags)

View file

@ -949,6 +949,14 @@ Please note that --` + MetricsHTTPFlag.Name + ` must be set to start the server.
Value: metrics.DefaultConfig.InfluxDBOrganization, Value: metrics.DefaultConfig.InfluxDBOrganization,
Category: flags.MetricsCategory, Category: flags.MetricsCategory,
} }
// <specular modification/>
EnableL2EngineApiFlag = &cli.BoolFlag{
Name: "enableL2EngineApi",
Usage: "Enable L2 Engine API",
Category: flags.EthCategory,
}
// </specular modification/>
) )
var ( var (
@ -1574,6 +1582,12 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.IsSet(MinerNewPayloadTimeout.Name) { if ctx.IsSet(MinerNewPayloadTimeout.Name) {
cfg.NewPayloadTimeout = ctx.Duration(MinerNewPayloadTimeout.Name) cfg.NewPayloadTimeout = ctx.Duration(MinerNewPayloadTimeout.Name)
} }
// <specular modification>
cfg.EnableL2EngineApi = ctx.Bool(EnableL2EngineApiFlag.Name)
if cfg.EnableL2EngineApi {
log.Info("L2 Engine API enabled")
}
// <specular modification/>
} }
func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) { func setRequiredBlocks(ctx *cli.Context, cfg *ethconfig.Config) {

View file

@ -320,7 +320,9 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// If the specified head matches with our local head, do nothing and keep // If the specified head matches with our local head, do nothing and keep
// generating the payload. It's a special corner case that a few slots are // generating the payload. It's a special corner case that a few slots are
// missing and we are requested to generate the payload in slot. // missing and we are requested to generate the payload in slot.
} else { // <specular modification>
} else if !api.eth.Miner().IsL2EngineApiEnabled() { // minor Engine API divergence: allow proposers to reorg their own chain
// <specular modification/>
// If the head block is already in our canonical chain, the beacon client is // If the head block is already in our canonical chain, the beacon client is
// probably resyncing. Ignore the update. // probably resyncing. Ignore the update.
log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number) log.Info("Ignoring beacon update to old head", "number", block.NumberU64(), "hash", update.HeadBlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0)), "have", api.eth.BlockChain().CurrentBlock().Number)
@ -364,6 +366,17 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
// sealed by the beacon client. The payload will be requested later, and we // sealed by the beacon client. The payload will be requested later, and we
// will replace it arbitrarily many times in between. // will replace it arbitrarily many times in between.
if payloadAttributes != nil { if payloadAttributes != nil {
// <specular modification>
transactions := make(types.Transactions, 0, len(payloadAttributes.Transactions))
for i, otx := range payloadAttributes.Transactions {
var tx types.Transaction
if err := tx.UnmarshalBinary(otx); err != nil {
return engine.STATUS_INVALID, fmt.Errorf("transaction %d is not valid: %v", i, err)
}
transactions = append(transactions, &tx)
}
// <specular modification/>
args := &miner.BuildPayloadArgs{ args := &miner.BuildPayloadArgs{
Parent: update.HeadBlockHash, Parent: update.HeadBlockHash,
Timestamp: payloadAttributes.Timestamp, Timestamp: payloadAttributes.Timestamp,
@ -371,6 +384,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
Random: payloadAttributes.Random, Random: payloadAttributes.Random,
Withdrawals: payloadAttributes.Withdrawals, Withdrawals: payloadAttributes.Withdrawals,
BeaconRoot: payloadAttributes.BeaconRoot, BeaconRoot: payloadAttributes.BeaconRoot,
// <specular modification>
NoTxPool: payloadAttributes.NoTxPool,
Transactions: transactions,
// <specular modification/>
} }
id := args.Id() id := args.Id()
// If we already are busy generating this work, then we do not need // If we already are busy generating this work, then we do not need
@ -716,6 +733,12 @@ func (api *ConsensusAPI) invalid(err error, latestValid *types.Header) engine.Pa
// //
// TODO(karalabe): Spin this goroutine down somehow // TODO(karalabe): Spin this goroutine down somehow
func (api *ConsensusAPI) heartbeat() { func (api *ConsensusAPI) heartbeat() {
// <specular modification>
if api.eth.Miner().IsL2EngineApiEnabled() { // don't start the api heartbeat, there is no transition
return
}
// <specular modification/>
// Sleep a bit on startup since there's obviously no beacon client yet // Sleep a bit on startup since there's obviously no beacon client yet
// attached, so no need to print scary warnings to the user. // attached, so no need to print scary warnings to the user.
time.Sleep(beaconUpdateStartupTimeout) time.Sleep(beaconUpdateStartupTimeout)

View file

@ -18,6 +18,7 @@
package miner package miner
import ( import (
"context"
"fmt" "fmt"
"math/big" "math/big"
"sync" "sync"
@ -31,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -43,6 +45,13 @@ type Backend interface {
TxPool() *txpool.TxPool TxPool() *txpool.TxPool
} }
// <specular modification>
type BackendWithHistoricalState interface {
StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error)
}
// <specular modification/>
// Config is the configuration parameters of mining. // Config is the configuration parameters of mining.
type Config struct { type Config struct {
Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards Etherbase common.Address `toml:",omitempty"` // Public address for block mining rewards
@ -53,6 +62,10 @@ type Config struct {
Recommit time.Duration // The time interval for miner to re-create mining work. Recommit time.Duration // The time interval for miner to re-create mining work.
NewPayloadTimeout time.Duration // The maximum time allowance for creating a new payload NewPayloadTimeout time.Duration // The maximum time allowance for creating a new payload
// <specular modification>
EnableL2EngineApi bool `toml:",omitempty"`
// <specular modification/>
} }
// DefaultConfig contains default settings for miner. // DefaultConfig contains default settings for miner.
@ -244,3 +257,10 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) { func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) {
return miner.worker.buildPayload(args) return miner.worker.buildPayload(args)
} }
// <specular modification>
func (miner *Miner) IsL2EngineApiEnabled() bool {
return miner.worker.config.EnableL2EngineApi
}
// <specular modification/>

View file

@ -41,6 +41,10 @@ type BuildPayloadArgs struct {
Random common.Hash // The provided randomness value Random common.Hash // The provided randomness value
Withdrawals types.Withdrawals // The provided withdrawals Withdrawals types.Withdrawals // The provided withdrawals
BeaconRoot *common.Hash // The provided beaconRoot (Cancun) BeaconRoot *common.Hash // The provided beaconRoot (Cancun)
// <specular modification>
NoTxPool bool // Specular addition: option to disable tx pool contents from being included
Transactions []*types.Transaction // Specular addition: txs forced into the block via engine API
// <specular modification/>
} }
// Id computes an 8-byte identifier by hashing the components of the payload arguments. // Id computes an 8-byte identifier by hashing the components of the payload arguments.
@ -55,6 +59,16 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
if args.BeaconRoot != nil { if args.BeaconRoot != nil {
hasher.Write(args.BeaconRoot[:]) hasher.Write(args.BeaconRoot[:])
} }
// <specular modification>
if args.NoTxPool || len(args.Transactions) > 0 { // extend if extra payload attributes are used
binary.Write(hasher, binary.BigEndian, args.NoTxPool)
binary.Write(hasher, binary.BigEndian, uint64(len(args.Transactions)))
for _, tx := range args.Transactions {
h := tx.Hash()
hasher.Write(h[:])
}
}
// <specular modification/>
var out engine.PayloadID var out engine.PayloadID
copy(out[:], hasher.Sum(nil)[:8]) copy(out[:], hasher.Sum(nil)[:8])
return out return out
@ -188,6 +202,9 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
withdrawals: args.Withdrawals, withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot, beaconRoot: args.BeaconRoot,
noTxs: true, noTxs: true,
// <specular modification>
txs: args.Transactions,
// <specular modification/>
} }
empty := w.getSealingBlock(emptyParams) empty := w.getSealingBlock(emptyParams)
if empty.err != nil { if empty.err != nil {
@ -196,6 +213,14 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
// Construct a payload object for return. // Construct a payload object for return.
payload := newPayload(empty.block, args.Id()) payload := newPayload(empty.block, args.Id())
// <specular modification>
if args.NoTxPool { // don't start the background payload updating job if there is no tx pool to pull from
// make sure to make it appear as full, otherwise it will wait indefinitely for payload building to complete.
payload.full = empty.block
payload.fullFees = empty.fees
return payload, nil
}
// <specular modification/>
// Spin up a routine for updating the payload in background. This strategy // Spin up a routine for updating the payload in background. This strategy
// can maximum the revenue for including transactions with highest fee. // can maximum the revenue for including transactions with highest fee.

View file

@ -17,6 +17,7 @@
package miner package miner
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -33,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool" "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/tracers"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -705,6 +707,18 @@ func (w *worker) makeEnv(parent *types.Header, header *types.Header, coinbase co
// Retrieve the parent state to execute on top and start a prefetcher for // Retrieve the parent state to execute on top and start a prefetcher for
// the miner to speed block sealing up a bit. // the miner to speed block sealing up a bit.
state, err := w.chain.StateAt(parent.Root) state, err := w.chain.StateAt(parent.Root)
// <specular modification>
if w.config.EnableL2EngineApi { // Allow the miner to reorg its own chain arbitrarily deep
if historicalBackend, ok := w.eth.(BackendWithHistoricalState); ok {
var release tracers.StateReleaseFunc
parentBlock := w.eth.BlockChain().GetBlockByHash(parent.Hash())
state, release, err = historicalBackend.StateAtBlock(context.Background(), parentBlock, ^uint64(0), nil, false, false)
state = state.Copy()
release()
}
}
// <specular modification/>
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -885,6 +899,9 @@ type generateParams struct {
withdrawals types.Withdrawals // List of withdrawals to include in block. withdrawals types.Withdrawals // List of withdrawals to include in block.
beaconRoot *common.Hash // The beacon root (cancun field). beaconRoot *common.Hash // The beacon root (cancun field).
noTxs bool // Flag whether an empty block without any transaction is expected noTxs bool // Flag whether an empty block without any transaction is expected
// <specular modification>
txs types.Transactions // Transactions to include at the start of the block
// <specular modification/>
} }
// prepareWork constructs the sealing task according to the given parameters, // prepareWork constructs the sealing task according to the given parameters,
@ -921,9 +938,12 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
Coinbase: genParams.coinbase, Coinbase: genParams.coinbase,
} }
// Set the extra field. // Set the extra field.
if len(w.extra) != 0 { // <specular modification>
if len(w.extra) != 0 && !w.config.EnableL2EngineApi { // L2 chains must not set any extra data.
header.Extra = w.extra header.Extra = w.extra
} }
// <specular modification/>
// Set the randomness field from the beacon chain if it's available. // Set the randomness field from the beacon chain if it's available.
if genParams.random != (common.Hash{}) { if genParams.random != (common.Hash{}) {
header.MixDigest = genParams.random header.MixDigest = genParams.random
@ -1008,6 +1028,18 @@ func (w *worker) generateWork(params *generateParams) *newPayloadResult {
return &newPayloadResult{err: err} return &newPayloadResult{err: err}
} }
defer work.discard() defer work.discard()
// <specular modification>
for _, tx := range params.txs {
from, _ := types.Sender(work.signer, tx)
work.state.SetTxContext(tx.Hash(), work.tcount)
_, err := w.commitTransaction(work, tx)
if err != nil {
return &newPayloadResult{err: fmt.Errorf("failed to force-include tx: %s type: %d sender: %s nonce: %d, err: %w", tx.Hash(), tx.Type(), from, tx.Nonce(), err)}
}
work.tcount++
}
// forced transactions done, fill rest of block with transactions
// <specular modification/>
if !params.noTxs { if !params.noTxs {
interrupt := new(atomic.Int32) interrupt := new(atomic.Int32)