Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@
branch = devel
[submodule "vendor/nim-web3"]
path = vendor/nim-web3
url = https://github.com/status-im/nim-web3.git
url = https://github.com/RazorClient/nim-web3.git
ignore = untracked
branch = master
branch = Focil-upstream
[submodule "vendor/nim-nat-traversal"]
path = vendor/nim-nat-traversal
url = https://github.com/status-im/nim-nat-traversal.git
Expand Down
1 change: 1 addition & 0 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ type
syncCommitteeMsgPool*: ref SyncCommitteeMsgPool
lightClientPool*: ref LightClientPool
validatorChangePool*: ref ValidatorChangePool
inclusionListStore*: ref InclusionListStore
elManager*: ELManager
restServer*: RestServerRef
keymanagerHost*: ref KeymanagerHost
Expand Down
12 changes: 11 additions & 1 deletion beacon_chain/consensus_object_pools/attestation_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import
chronicles, stew/byteutils,
# Internal
../spec/[
beaconstate, eth2_merkleization, forks, state_transition_epoch, validator],
beaconstate, eth2_merkleization, forks, state_transition_epoch, validator,datatypes/focil],
"."/[spec_cache, blockchain_dag, block_quarantine],
../fork_choice/fork_choice,
../beacon_clock
Expand Down Expand Up @@ -208,6 +208,16 @@ proc addForkChoiceVotes(
# hopefully the fork choice will heal itself over time.
error "Couldn't add attestation to fork choice, bug?", err = v.error()

proc onInclusionList*(pool: var AttestationPool,
inclusionList: focil.SignedInclusionList,
wallTime: BeaconTime) =
let res = pool.forkChoice.on_inclusion_list(pool.dag, inclusionList, wallTime)
if res.isErr():
warn "Couldn't add inclusion list to fork choice",
validator_index = inclusionList.message.validator_index,
slot = inclusionList.message.slot,
err = res.error()

func candidateIdx(
pool: AttestationPool, slot: Slot, candidateIdxType: CandidateIdxType):
Opt[int] =
Expand Down
87 changes: 87 additions & 0 deletions beacon_chain/consensus_object_pools/inclusion_list_pool.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# beacon_chain
# Copyright (c) 2024-2025 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.

{.push raises: [].}

import
# Standard libraries
std/[deques, sets],
# Internal
../spec/datatypes/[base, focil],
../spec/[helpers, state_transition_block],
"."/[blockchain_dag]

export base, deques, blockchain_dag, focil

const
INCLUSION_LISTS_BOUND = 1024'u64 # Reasonable bound for inclusion lists

type
OnInclusionListCallback =
proc(data: SignedInclusionList) {.gcsafe, raises: [].}

InclusionListPool* = object
## The inclusion list pool tracks signed inclusion lists that could be
## added to a proposed block.

inclusion_lists*: Deque[SignedInclusionList] ## \
## Not a function of chain DAG branch; just used as a FIFO queue for blocks

prior_seen_inclusion_list_validators: HashSet[uint64] ## \
## Records validator indices that have already submitted inclusion lists
## to prevent duplicate processing

dag*: ChainDAGRef
onInclusionListReceived*: OnInclusionListCallback

func init*(T: type InclusionListPool, dag: ChainDAGRef,
onInclusionList: OnInclusionListCallback = nil): T =
## Initialize an InclusionListPool from the dag `headState`
T(
inclusion_lists:
initDeque[SignedInclusionList](initialSize = INCLUSION_LISTS_BOUND.int),
dag: dag,
onInclusionListReceived: onInclusionList)

func addInclusionListMessage(
subpool: var Deque[SignedInclusionList],
seenpool: var HashSet[uint64],
inclusionList: SignedInclusionList,
bound: static[uint64]) =
## Add an inclusion list message to the pool, maintaining bounds
while subpool.lenu64 >= bound:
seenpool.excl subpool.popFirst().message.validator_index.uint64

subpool.addLast(inclusionList)
doAssert subpool.lenu64 <= bound

func isSeen*(pool: InclusionListPool, msg: SignedInclusionList): bool =
## Check if we've already seen an inclusion list from this validator
msg.message.validator_index.uint64 in pool.prior_seen_inclusion_list_validators

proc addMessage*(pool: var InclusionListPool, msg: SignedInclusionList) =
## Add an inclusion list message to the pool
pool.prior_seen_inclusion_list_validators.incl(
msg.message.validator_index.uint64)

addInclusionListMessage(
pool.inclusion_lists, pool.prior_seen_inclusion_list_validators, msg, INCLUSION_LISTS_BOUND)

# Send notification about new inclusion list via callback
if not(isNil(pool.onInclusionListReceived)):
pool.onInclusionListReceived(msg)

func getInclusionLists*(pool: InclusionListPool): seq[SignedInclusionList] =
## Get all inclusion lists in the pool
result = newSeq[SignedInclusionList](pool.inclusion_lists.len)
for i, inclusionList in pool.inclusion_lists:
result[i] = inclusionList

func clear*(pool: var InclusionListPool) =
## Clear all inclusion lists from the pool
pool.inclusion_lists.clear()
pool.prior_seen_inclusion_list_validators.clear()
58 changes: 57 additions & 1 deletion beacon_chain/el/el_manager.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ from std/times import getTime, inSeconds, initTime, `-`
from ../spec/engine_authentication import getSignedIatToken
from ../spec/helpers import bytes_to_uint64
from ../spec/state_transition_block import kzg_commitment_to_versioned_hash
from json_rpc/router import METHOD_NOT_FOUND

export
eth1_chain, el_conf, engine_api, base
Expand Down Expand Up @@ -204,6 +205,9 @@ type

lastPayloadId: Opt[Bytes8]

supportsInclusionListFetch: Opt[bool]
supportsInclusionListUpdate: Opt[bool]

FullBlockId* = object
number: Eth1BlockNumber
hash: Hash32
Expand Down Expand Up @@ -463,6 +467,55 @@ proc connectedRpcClient(connection: ELConnection): Future[RpcClient] {.

connection.web3.get.provider

proc getInclusionListFromSingleEL(
connection: ELConnection,
parentHash: Eth2Digest
): Future[Opt[seq[bellatrix.Transaction]]] {.async: (raises: [CatchableError]).} =
if connection.supportsInclusionListFetch.isSome and
not connection.supportsInclusionListFetch.get:
return Opt.none(seq[bellatrix.Transaction])

let rpcClient = await connection.connectedRpcClient()

try:
let response =
await rpcClient.engine_getInclusionListV1(parentHash.asBlockHash)
connection.supportsInclusionListFetch = Opt.some(true)
return Opt.some(response.toConsensusTransactions())
except ErrorResponse as exc:
if exc.status == METHOD_NOT_FOUND:
if connection.supportsInclusionListFetch.isNone:
trace "Execution client does not support engine_getInclusionListV1",
url = connection.engineUrl.url
connection.supportsInclusionListFetch = Opt.some(false)
return Opt.none(seq[bellatrix.Transaction])
raise exc

proc updatePayloadInclusionListForSingleEL(
connection: ELConnection,
payloadId: Bytes8,
inclusionList: InclusionList
): Future[bool] {.async: (raises: [CatchableError]).} =
if connection.supportsInclusionListUpdate.isSome and
not connection.supportsInclusionListUpdate.get:
return false

let rpcClient = await connection.connectedRpcClient()

try:
discard await rpcClient.engine_updatePayloadWithinInclusionListV1(
payloadId, inclusionList)
connection.supportsInclusionListUpdate = Opt.some(true)
return true
except ErrorResponse as exc:
if exc.status == METHOD_NOT_FOUND:
if connection.supportsInclusionListUpdate.isNone:
trace "Execution client does not support engine_updatePayloadWithinInclusionListV1",
url = connection.engineUrl.url
connection.supportsInclusionListUpdate = Opt.some(false)
return false
raise exc

proc getBlockByHash(
rpcClient: RpcClient,
hash: Hash32
Expand Down Expand Up @@ -1574,7 +1627,10 @@ template getBlockProposalData*(m: ELManager,
func new*(T: type ELConnection, engineUrl: EngineApiUrl): T =
ELConnection(
engineUrl: engineUrl,
depositContractSyncStatus: DepositContractSyncStatus.unknown)
depositContractSyncStatus: DepositContractSyncStatus.unknown,
lastPayloadId: Opt[Bytes8].none,
supportsInclusionListFetch: Opt[bool].none,
supportsInclusionListUpdate: Opt[bool].none)

proc new*(T: type ELManager,
cfg: RuntimeConfig,
Expand Down
18 changes: 17 additions & 1 deletion beacon_chain/el/engine_api_conversions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import
kzg4844/[kzg_abi, kzg],
../spec/datatypes/[bellatrix, capella, deneb, electra, fulu],
../spec/datatypes/[bellatrix, capella, deneb, electra, fulu,focil],
web3/[engine_api, engine_api_types]

from std/sequtils import mapIt
Expand Down Expand Up @@ -156,6 +156,22 @@ func asElectraConsensusPayload(rpcExecutionPayload: ExecutionPayloadV3):
blob_gas_used: rpcExecutionPayload.blobGasUsed.uint64,
excess_blob_gas: rpcExecutionPayload.excessBlobGas.uint64)

func toConsensusTransactions*(inclusionList: InclusionList):
seq[bellatrix.Transaction] =
## Convert Engine API inclusion list transactions to consensus transactions.
let txs = inclusionList.distinctBase
result = newSeqOfCap[bellatrix.Transaction](txs.len)
for tx in txs:
result.add bellatrix.Transaction.init(tx.distinctBase)

func toEngineInclusionList*(txs: seq[bellatrix.Transaction]): InclusionList =
## Convert consensus inclusion list transactions to Engine API representation.
var engineTxs = newSeqOfCap[TypedTransaction](txs.len)
for tx in txs:
engineTxs.add TypedTransaction(tx.asSeq)
InclusionList(engineTxs)


func asFuluConsensusPayload(rpcExecutionPayload: ExecutionPayloadV3):
fulu.ExecutionPayload =
template getTransaction(tt: TypedTransaction): bellatrix.Transaction =
Expand Down
Loading