diff --git a/beacon/engine/types.go b/beacon/engine/types.go index 874f3e90af..05ae198ef1 100644 --- a/beacon/engine/types.go +++ b/beacon/engine/types.go @@ -63,6 +63,8 @@ type ExecutableData struct { Withdrawals []*types.Withdrawal `json:"withdrawals"` BlobGasUsed *uint64 `json:"blobGasUsed"` ExcessBlobGas *uint64 `json:"excessBlobGas"` + + Summary []*InclusionListEntry `json:"summary"` } // JSON type overrides for executableData. @@ -277,3 +279,15 @@ type ExecutionPayloadBodyV1 struct { TransactionData []hexutil.Bytes `json:"transactions"` Withdrawals []*types.Withdrawal `json:"withdrawals"` } + +// InclusionListV1 is used in the response to GetInclusionListV1 and request to NewInclusionListV1 +type InclusionListV1 struct { + Summary []*InclusionListEntry `json:"summary"` + Transactions []*types.Transaction `json:"transactions"` +} + +// InclusionListEntry denotes a summary entry of (address, gasLimit) +type InclusionListEntry struct { + address common.Address `json:"address"` + gasLimit uint32 `json:"gasLimit"` // TODO(manav): change to uint8 +} diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go index a7381ac6e7..c440dafd23 100644 --- a/core/txpool/blobpool/blobpool.go +++ b/core/txpool/blobpool/blobpool.go @@ -1526,3 +1526,8 @@ func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus { } return txpool.TxStatusUnknown } + +// GetInclusionList returns an inclusion list from the pool containing pairs +// of transaction summary and data which are executable. Currently blob txs +// aren't supported in the inclusion list. +func (pool *BlobPool) GetInclusionList() *types.InclusionList { return nil } diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go index 00e326c4b8..bc6233de11 100644 --- a/core/txpool/legacypool/legacypool.go +++ b/core/txpool/legacypool/legacypool.go @@ -51,6 +51,14 @@ const ( // more expensive to propagate; larger transactions also take more resources // to validate whether they fit into the pool or not. txMaxSize = 4 * txSlotSize // 128KB + + // maxTxsPerInclusionList is the maximum number of transactions that can be + // included in an inclusion list (MAX_TRANSACTIONS_PER_INCLUSION_LIST). + maxTxsPerInclusionList = 16 + + // maxGasPerInclusionList is the maximum amount of gas that can be included + // in an inclusion list (INCLUSION_LIST_MAX_GAS). + maxGasPerInclusionList = 5000000 // 5M ) var ( @@ -1670,6 +1678,57 @@ func (pool *LegacyPool) demoteUnexecutables() { } } +// GetInclusionList returns an inclusion list from the pool containing pairs +// of transaction summary and data which are executable. +func (pool *LegacyPool) GetInclusionList() *types.InclusionList { + // The following conditions should be satisfied while filling the inclusion list. + // 1. The size of IL should not exceed `maxTxsPerInclusionList` + // + // 2. The total gas limit should not exceed `maxGasPerInclusionList` + // + // 3. The `maxFeePerGas` of all the transactions should be 1.125 times + // the `baseFee` of the current block to account for increase in the + // next block due to EIP 1559. + summaries := make([]*types.InclusionListEntry, 0, maxTxsPerInclusionList) + transactions := make([]*types.Transaction, 0, maxTxsPerInclusionList) + + // TODO(manav2401): Not sure what's the best way to fetch transactions for inclusion list. + // Few possibilities are: prioritise locals first, borrow the tx ordering + // logic from miner if we want to choose best transactions. + + // TODO(manav): This is just a brute force logic - has a lot of scope for improvement + remoteTxs := pool.Pending(true) + for _, account := range pool.Locals() { + if txs := remoteTxs[account]; len(txs) > 0 { + delete(remoteTxs, account) + + // TODO(manav): Decide if we want multiple txs from the same account. + for _, lasyTx := range txs { + tx := lasyTx.Resolve() + // TODO(manav): Perform checks for (2) and (3) here. + summary := types.InclusionListEntry{ + Address: account, + GasLimit: uint32(tx.Gas()), + } + summaries = append(summaries, &summary) + transactions = append(transactions, tx) + if len(summaries) == maxTxsPerInclusionList { + break + } + } + + if len(summaries) == maxTxsPerInclusionList { + break + } + } + } + + // If we still have space, fill with remote txs + // TODO(manav): borrow ordering logic from miner OR write a simple one for IL + + return &types.InclusionList{Summary: summaries, Transactions: transactions} +} + // addressByHeartbeat is an account address tagged with its last activity timestamp. type addressByHeartbeat struct { address common.Address diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go index 85312c4318..8b1f7f452a 100644 --- a/core/txpool/subpool.go +++ b/core/txpool/subpool.go @@ -121,6 +121,10 @@ type SubPool interface { // Locals retrieves the accounts currently considered local by the pool. Locals() []common.Address + // GetInclusionList returns an inclusion list from the pool containing pairs + // of transaction summary and data which are executable. + GetInclusionList() *types.InclusionList + // Status returns the known status (unknown/pending/queued) of a transaction // identified by their hashes. Status(hash common.Hash) TxStatus diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go index e40b414054..9737d8d51f 100644 --- a/core/txpool/txpool.go +++ b/core/txpool/txpool.go @@ -413,3 +413,14 @@ func (p *TxPool) Status(hash common.Hash) TxStatus { } return TxStatusUnknown } + +// GetInclusionList returns an inclusion list from the pool containing pairs +// of transaction summary and data which are executable. +func (p *TxPool) GetInclusionList() *types.InclusionList { + for _, subpool := range p.subpools { + if list := subpool.GetInclusionList(); list != nil { + return list + } + } + return nil +} diff --git a/core/types/block.go b/core/types/block.go index 6f897121df..710429e60d 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -173,6 +173,18 @@ type Body struct { Withdrawals []*Withdrawal `rlp:"optional"` } +// InclusionList represents pairs of transaction summary and the transaction data itself +type InclusionList struct { + Summary []*InclusionListEntry `json:"summary"` + Transactions []*Transaction `json:"transactions"` +} + +// InclusionListEntry denotes a summary entry of (address, gasLimit) +type InclusionListEntry struct { + Address common.Address `json:"address"` + GasLimit uint32 `json:"gasLimit"` // TODO(manav): change to uint8 +} + // Block represents an Ethereum block. // // Note the Block type tries to be 'immutable', and contains certain caches that rely @@ -196,6 +208,8 @@ type Block struct { transactions Transactions withdrawals Withdrawals + summary []*InclusionListEntry + // caches hash atomic.Value size atomic.Value diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index f6c7ab09c7..1eae8eaf7b 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -87,6 +87,9 @@ var caps = []string{ "engine_newPayloadV3", "engine_getPayloadBodiesByHashV1", "engine_getPayloadBodiesByRangeV1", + "engine_getInclusionListV1", + "engine_newInclusionListV1", + "engine_newPayloadVePBS", } type ConsensusAPI struct { @@ -156,6 +159,27 @@ func newConsensusAPIWithoutHeartbeat(eth *eth.Ethereum) *ConsensusAPI { return api } +// GetInclusionListV1 returns an inclusion list which contains summary + list of transactions +// which are valid for the current slot. +func (api *ConsensusAPI) GetInclusionListV1() (*engine.InclusionListV1, error) { + // TODO(manav): Do we check here if we're on correct fork or are fully synced? If not + // we might end up delivering wrong IL. Other will reject it though but do we want to + // risk it? + // Call txpool.GetInclusion list here + return nil, nil +} + +// NewInclusionListV1 validates whether an inclusion list (summary + txs) is +// correct for the current state or not. +func (api *ConsensusAPI) NewInclusionListV1() bool { + return false +} + +// TODO: update/define executable params +func (api *ConsensusAPI) NewPayloadVePBS() bool { + return false +} + // ForkchoiceUpdatedV1 has several responsibilities: // // We try to set our blockchain to the headBlock.