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"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
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
enc.Timestamp = hexutil.Uint64(p.Timestamp)
@ -28,6 +32,15 @@ func (p PayloadAttributes) MarshalJSON() ([]byte, error) {
enc.SuggestedFeeRecipient = p.SuggestedFeeRecipient
enc.Withdrawals = p.Withdrawals
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)
}
@ -39,6 +52,10 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
SuggestedFeeRecipient *common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
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
if err := json.Unmarshal(input, &dec); err != nil {
@ -62,5 +79,16 @@ func (p *PayloadAttributes) UnmarshalJSON(input []byte) error {
if dec.BeaconRoot != nil {
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
}

View file

@ -36,11 +36,21 @@ type PayloadAttributes struct {
SuggestedFeeRecipient common.Address `json:"suggestedFeeRecipient" gencodec:"required"`
Withdrawals []*types.Withdrawal `json:"withdrawals"`
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.
type payloadAttributesMarshaling struct {
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

View file

@ -144,6 +144,9 @@ var (
utils.GpoPercentileFlag,
utils.GpoMaxGasPriceFlag,
utils.GpoIgnoreGasPriceFlag,
// <specular modification>
utils.EnableL2EngineApiFlag,
// <specular modification/>
configFileFlag,
}, 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,
Category: flags.MetricsCategory,
}
// <specular modification/>
EnableL2EngineApiFlag = &cli.BoolFlag{
Name: "enableL2EngineApi",
Usage: "Enable L2 Engine API",
Category: flags.EthCategory,
}
// </specular modification/>
)
var (
@ -1574,6 +1582,12 @@ func setMiner(ctx *cli.Context, cfg *miner.Config) {
if ctx.IsSet(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) {

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
// 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.
} 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
// 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)
@ -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
// will replace it arbitrarily many times in between.
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{
Parent: update.HeadBlockHash,
Timestamp: payloadAttributes.Timestamp,
@ -371,6 +384,10 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl
Random: payloadAttributes.Random,
Withdrawals: payloadAttributes.Withdrawals,
BeaconRoot: payloadAttributes.BeaconRoot,
// <specular modification>
NoTxPool: payloadAttributes.NoTxPool,
Transactions: transactions,
// <specular modification/>
}
id := args.Id()
// 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
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
// attached, so no need to print scary warnings to the user.
time.Sleep(beaconUpdateStartupTimeout)

View file

@ -18,6 +18,7 @@
package miner
import (
"context"
"fmt"
"math/big"
"sync"
@ -31,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"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/log"
"github.com/ethereum/go-ethereum/params"
@ -43,6 +45,13 @@ type Backend interface {
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.
type Config struct {
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.
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.
@ -244,3 +257,10 @@ func (miner *Miner) SubscribePendingLogs(ch chan<- []*types.Log) event.Subscript
func (miner *Miner) BuildPayload(args *BuildPayloadArgs) (*Payload, error) {
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
Withdrawals types.Withdrawals // The provided withdrawals
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.
@ -55,6 +59,16 @@ func (args *BuildPayloadArgs) Id() engine.PayloadID {
if args.BeaconRoot != nil {
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
copy(out[:], hasher.Sum(nil)[:8])
return out
@ -188,6 +202,9 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
withdrawals: args.Withdrawals,
beaconRoot: args.BeaconRoot,
noTxs: true,
// <specular modification>
txs: args.Transactions,
// <specular modification/>
}
empty := w.getSealingBlock(emptyParams)
if empty.err != nil {
@ -196,6 +213,14 @@ func (w *worker) buildPayload(args *BuildPayloadArgs) (*Payload, error) {
// Construct a payload object for return.
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
// can maximum the revenue for including transactions with highest fee.

View file

@ -17,6 +17,7 @@
package miner
import (
"context"
"errors"
"fmt"
"math/big"
@ -33,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"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/log"
"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
// the miner to speed block sealing up a bit.
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 {
return nil, err
}
@ -885,6 +899,9 @@ type generateParams struct {
withdrawals types.Withdrawals // List of withdrawals to include in block.
beaconRoot *common.Hash // The beacon root (cancun field).
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,
@ -921,9 +938,12 @@ func (w *worker) prepareWork(genParams *generateParams) (*environment, error) {
Coinbase: genParams.coinbase,
}
// 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
}
// <specular modification/>
// Set the randomness field from the beacon chain if it's available.
if genParams.random != (common.Hash{}) {
header.MixDigest = genParams.random
@ -1008,6 +1028,18 @@ func (w *worker) generateWork(params *generateParams) *newPayloadResult {
return &newPayloadResult{err: err}
}
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 {
interrupt := new(atomic.Int32)