Skip to content

Commit f5ededd

Browse files
agnxshtersec
andauthored
track column verification state per popSidecars (#8841)
* track column verification in column quarantine * update some comments * review * redesign approach * cleanup * rework some parts * remove unused imports * items() isn't perfect, and it's not an iterator like sequtils items, but better than all() --------- Co-authored-by: tersec <tersec@users.noreply.github.com>
1 parent e9dca82 commit f5ededd

7 files changed

Lines changed: 131 additions & 38 deletions

File tree

beacon_chain/el/el_getblobs_service.nim

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ proc attemptGetBlobs*(
182182
# skip the EL fetch and enqueue with the existing columns.
183183
if forkyBlck.root in self.columnFirstFetched:
184184
let sidecarsOpt =
185-
self.fuluColumnQuarantine[].popSidecars(forkyBlck.root)
185+
self.fuluColumnQuarantine[].popSidecarsForImport(forkyBlck.root)
186186
if sidecarsOpt.isSome():
187187
if not quarantine[].removeSidecarless(forkyBlck.root):
188188
return
@@ -318,7 +318,8 @@ proc attemptGetBlobs*(
318318
# column sidecars we just installed.
319319
self.partialColumnQuarantine[].pruneForBlock(forkyBlck.root)
320320

321-
let sidecarsOpt = self.fuluColumnQuarantine[].popSidecars(forkyBlck.root)
321+
let sidecarsOpt =
322+
self.fuluColumnQuarantine[].popSidecarsForImport(forkyBlck.root)
322323

323324
self.blockProcessor.enqueueBlock(MsgSource.gossip, forkyBlck, sidecarsOpt)
324325
elif consensusFork == ConsensusFork.Heze:

beacon_chain/gossip_processing/block_processor.nim

Lines changed: 57 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import
1414
../sszdump
1515

1616
from std/deques import Deque, addLast, contains, initDeque, items, len, shrink
17-
from std/sequtils import anyIt, filterIt
17+
from std/sequtils import anyIt
1818
from ../consensus_object_pools/consensus_manager import
1919
ConsensusManager, to, updateHead, updateExecutionHead, checkExpectedEnvelope
2020
from ../consensus_object_pools/blockchain_dag import
@@ -130,11 +130,39 @@ type
130130

131131
NoSidecars* = typeof(())
132132
SomeOptSidecars =
133-
NoSidecars | Opt[BlobSidecars] | Opt[fulu.DataColumnSidecars] |
133+
NoSidecars | Opt[BlobSidecars] | Opt[fulu.DataColumnSidecarsForImport] |
134134
Opt[gloas.DataColumnSidecars]
135135

136136
const noSidecars* = default(NoSidecars)
137137

138+
proc popSidecarsForImport*(
139+
quarantine: var FuluColumnQuarantine,
140+
blockRoot: Eth2Digest
141+
): Opt[fulu.DataColumnSidecarsForImport] =
142+
## Pop the columns of ``blockRoot``, split by whether their KZG proofs have
143+
## already been checked. The drain has to stay adjacent to the pop: read any
144+
## later, it could answer for another set of columns popped for the same root.
145+
let sidecars = quarantine.popSidecars(blockRoot).valueOr:
146+
return Opt.none(fulu.DataColumnSidecarsForImport)
147+
let unverified = quarantine.popPendingVerify(blockRoot)
148+
149+
var res: fulu.DataColumnSidecarsForImport
150+
for sidecar in sidecars:
151+
if sidecar[].index in unverified:
152+
res.untrusted.add(sidecar)
153+
else:
154+
res.trusted.add(sidecar)
155+
Opt.some(res)
156+
157+
proc popSidecarsForImport*(
158+
quarantine: var GloasColumnQuarantine,
159+
blockRoot: Eth2Digest
160+
): Opt[gloas.DataColumnSidecars] =
161+
## Gloas checks every column in `addPayload`; only the drain matters here.
162+
let sidecars = quarantine.popSidecars(blockRoot)
163+
discard quarantine.popPendingVerify(blockRoot)
164+
sidecars
165+
138166
# Initialization
139167
# ------------------------------------------------------------------------------
140168

@@ -225,11 +253,12 @@ proc verifySidecars(
225253

226254
proc verifySidecars(
227255
signedBlock: fulu.SignedBeaconBlock,
228-
sidecarsOpt: Opt[fulu.DataColumnSidecars],
256+
sidecarsOpt: Opt[fulu.DataColumnSidecarsForImport],
229257
): Result[void, VerifierError] =
230258
sidecarsOpt.isErrOr:
231-
if value.len > 0 and signedBlock.message.body.blob_kzg_commitments.len > 0:
232-
verify_data_column_sidecar_kzg_proofs(value).isOkOr:
259+
if value.untrusted.len > 0 and
260+
signedBlock.message.body.blob_kzg_commitments.len > 0:
261+
verify_data_column_sidecar_kzg_proofs(value.untrusted).isOkOr:
233262
debug "data column validation failed",
234263
blockRoot = shortLog(signedBlock.root),
235264
blck = shortLog(signedBlock.message),
@@ -240,17 +269,23 @@ proc verifySidecars(
240269

241270
proc storeSidecars(
242271
self: BlockProcessor,
243-
sidecarsOpt: Opt[fulu.DataColumnSidecars] | Opt[gloas.DataColumnSidecars]
272+
sidecarsOpt: Opt[fulu.DataColumnSidecarsForImport] |
273+
Opt[gloas.DataColumnSidecars]
244274
) =
245275
sidecarsOpt.isErrOr:
246-
self.consensusManager.dag.db.putDataColumnSidecars(value)
247-
if self.onDataColumnsStored != nil and value.len > 0:
276+
let sidecars =
277+
when value is gloas.DataColumnSidecars:
278+
value
279+
else:
280+
value.items()
281+
self.consensusManager.dag.db.putDataColumnSidecars(sidecars)
282+
if self.onDataColumnsStored != nil and sidecars.len > 0:
248283
let slot =
249284
when value is gloas.DataColumnSidecars:
250285
# [Modified in Gloas:EIP7732] carries `slot` directly.
251-
value[0][].slot
286+
sidecars[0][].slot
252287
else:
253-
value[0][].signed_block_header.message.slot
288+
sidecars[0][].signed_block_header.message.slot
254289
self.onDataColumnsStored(slot)
255290

256291
proc enqueuePayload*(self: ref BlockProcessor, blck: gloas.SignedBeaconBlock)
@@ -465,9 +500,9 @@ proc enqueueQuarantine(self: ref BlockProcessor, parent: BlockRef) =
465500
elif consensusFork == ConsensusFork.Fulu:
466501
let sidecarsOpt =
467502
if len(forkyBlck.message.body.blob_kzg_commitments) == 0:
468-
Opt.some(default(fulu.DataColumnSidecars))
503+
Opt.some(default(fulu.DataColumnSidecarsForImport))
469504
else:
470-
self.fuluColumnQuarantine[].popSidecars(forkyBlck.root)
505+
self.fuluColumnQuarantine[].popSidecarsForImport(forkyBlck.root)
471506
elif consensusFork in ConsensusFork.Phase0 .. ConsensusFork.Electra:
472507
const sidecarsOpt = noSidecars
473508
else:
@@ -630,7 +665,8 @@ proc enqueueFromDb(self: ref BlockProcessor, root: Eth2Digest) =
630665
sidecarsOk = false # Pruned, or inconsistent DB
631666
break
632667
data_column_sidecars.add data_column
633-
Opt.some data_column_sidecars
668+
# Columns reach the database only after their proofs were checked.
669+
Opt.some data_column_sidecars.toTrustedImport()
634670
else:
635671
noSidecars
636672

@@ -727,13 +763,7 @@ proc storeBlock(
727763
when consensusFork == ConsensusFork.Fulu:
728764
# Only request manager-sourced columns arrive unverified; getBlobsV2/V3/V4
729765
# and CL gossip are both either trusted or verified.
730-
let pendingVerify =
731-
self.fuluColumnQuarantine[].popPendingVerify(signedBlock.root)
732-
if not pendingVerify.empty:
733-
sidecarsOpt.isErrOr:
734-
let toVerify = value.filterIt(it[].index in pendingVerify)
735-
if toVerify.len > 0:
736-
?verifySidecars(signedBlock, Opt.some(toVerify))
766+
?verifySidecars(signedBlock, sidecarsOpt)
737767
debug "block_processor verifySidecars completed",
738768
verifySidecarsDur = Moment.now() - newPayloadTick,
739769
blck = shortLog(signedBlock.message),
@@ -933,10 +963,14 @@ proc addBlock*(
933963
# becomes canonical, it is vital to import it as quickly as possible.
934964
self.enqueueFromDb(blck.message.parent_root)
935965

936-
when sidecarsOpt is Opt[fulu.DataColumnSidecars]:
966+
when sidecarsOpt is Opt[fulu.DataColumnSidecarsForImport]:
937967
if sidecarsOpt.isSome:
968+
# Put them back as they came out, so trusted ones stay trusted.
969+
let sidecars = sidecarsOpt.get
970+
self.fuluColumnQuarantine[].put(
971+
blockRoot, sidecars.untrusted, verified = false)
938972
self.fuluColumnQuarantine[].put(
939-
blockRoot, sidecarsOpt.get, verified = false)
973+
blockRoot, sidecars.trusted.asSeq, verified = true)
940974
elif sidecarsOpt is Opt[gloas.DataColumnSidecars]:
941975
# In Gloas, block is enqueued with NoSidecar so we need not to care
942976
# about quarantine.
@@ -1117,7 +1151,7 @@ proc enqueuePayload*(self: ref BlockProcessor, blck: gloas.SignedBeaconBlock) =
11171151
if bid.message.blob_kzg_commitments.len() == 0:
11181152
Opt.some(default(gloas.DataColumnSidecars))
11191153
else:
1120-
self.gloasColumnQuarantine[].popSidecars(blck.root)
1154+
self.gloasColumnQuarantine[].popSidecarsForImport(blck.root)
11211155
if sidecarsOpt.isNone():
11221156
let dag = self.consensusManager.dag
11231157
# As sidecars are missing, put envelope back to quarantine.

beacon_chain/gossip_processing/eth2_processor.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,9 +311,9 @@ proc processSignedBeaconBlock*(
311311
elif consensusFork == ConsensusFork.Fulu:
312312
let sidecarsOpt =
313313
if len(signedBlock.message.body.blob_kzg_commitments) == 0:
314-
Opt.some(default(fulu.DataColumnSidecars))
314+
Opt.some(default(fulu.DataColumnSidecarsForImport))
315315
else:
316-
self.fuluColumnQuarantine[].popSidecars(signedBlock.root)
316+
self.fuluColumnQuarantine[].popSidecarsForImport(signedBlock.root)
317317
if sidecarsOpt.isNone():
318318
self.blockProcessor[].startExecutionValidity(signedBlock, wallTime)
319319
discard self.quarantine[].addSidecarless(self.dag.finalizedHead.slot, signedBlock)
@@ -426,7 +426,7 @@ proc processDataColumnSidecar*(
426426
block_root, dataColumnSidecar, verified = true)
427427

428428
if block_root in self.quarantine[].sidecarless:
429-
let cres = self.fuluColumnQuarantine[].popSidecars(block_root)
429+
let cres = self.fuluColumnQuarantine[].popSidecarsForImport(block_root)
430430
if cres.isSome():
431431
let blck = self.quarantine[].popSidecarless(block_root).expect("checked above")
432432
withBlck(blck):

beacon_chain/nimbus_beacon_node.nim

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ proc initFullNode(
664664
# Disable sidecars processing at block time.
665665
const sidecarsOpt = noSidecars
666666
else:
667-
let sidecarsOpt = Opt.none(fulu.DataColumnSidecars)
667+
let sidecarsOpt = Opt.none(fulu.DataColumnSidecarsForImport)
668668
elif consensusFork in ConsensusFork.Phase0 .. ConsensusFork.Electra:
669669
const sidecarsOpt = noSidecars
670670
else:
@@ -688,9 +688,9 @@ proc initFullNode(
688688
elif consensusFork == ConsensusFork.Fulu:
689689
let sidecarsOpt =
690690
if len(forkyBlck.message.body.blob_kzg_commitments) == 0:
691-
Opt.some(default(fulu.DataColumnSidecars))
691+
Opt.some(default(fulu.DataColumnSidecarsForImport))
692692
else:
693-
fuluColumnQuarantine[].popSidecars(forkyBlck.root)
693+
fuluColumnQuarantine[].popSidecarsForImport(forkyBlck.root)
694694
if sidecarsOpt.isNone():
695695
# We don't have all the columns for this block, so we have
696696
# to put it in columnless quarantine.
@@ -756,7 +756,7 @@ proc initFullNode(
756756
if bid.message.blob_kzg_commitments.len() == 0:
757757
Opt.some(default(gloas.DataColumnSidecars))
758758
else:
759-
gloasColumnQuarantine[].popSidecars(blockRoot)
759+
gloasColumnQuarantine[].popSidecarsForImport(blockRoot)
760760
if sidecarsOpt.isNone():
761761
# As sidecars are missing, put envelope back to quarantine.
762762
discard quarantine[].addSidecarless(dag.finalizedHead.slot, blck)

beacon_chain/spec/datatypes/fulu.nim

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ type
8585

8686
DataColumnSidecars* = seq[ref DataColumnSidecar]
8787

88+
# Column sidecars whose KZG proofs have already been checked.
89+
TrustedDataColumnSidecars* = distinct DataColumnSidecars
90+
91+
DataColumnSidecarsForImport* = object
92+
## All columns of one block, split by whether their proofs still need
93+
## checking.
94+
trusted*: TrustedDataColumnSidecars
95+
untrusted*: DataColumnSidecars
96+
8897
# https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.10/specs/fulu/p2p-interface.md#datacolumnidentifier
8998
DataColumnIdentifier* = object
9099
block_root*: Eth2Digest
@@ -544,6 +553,50 @@ template asTrusted*(
544553
SigVerifiedSignedBeaconBlock): TrustedSignedBeaconBlock =
545554
isomorphicCast[TrustedSignedBeaconBlock](x)
546555

556+
template asTrusted*(x: DataColumnSidecars): TrustedDataColumnSidecars =
557+
## Only for columns whose KZG proofs have actually been checked.
558+
TrustedDataColumnSidecars(x)
559+
560+
template asSeq*(x: TrustedDataColumnSidecars): DataColumnSidecars =
561+
DataColumnSidecars(x)
562+
563+
func len*(x: TrustedDataColumnSidecars): int {.borrow.}
564+
565+
# `borrow` cannot match `seq`'s generic `T` return, so these convert explicitly
566+
func `[]`*(x: TrustedDataColumnSidecars, i: int): ref DataColumnSidecar =
567+
DataColumnSidecars(x)[i]
568+
569+
func add*(x: var TrustedDataColumnSidecars, v: ref DataColumnSidecar) =
570+
DataColumnSidecars(x).add(v)
571+
572+
func len*(sidecars: DataColumnSidecarsForImport): int =
573+
len(sidecars.trusted) + len(sidecars.untrusted)
574+
575+
func items*(sidecars: DataColumnSidecarsForImport): DataColumnSidecars =
576+
## Every column, in ascending index order.
577+
var
578+
res = newSeqOfCap[ref DataColumnSidecar](len(sidecars))
579+
i, j = 0
580+
while i < len(sidecars.trusted) and j < len(sidecars.untrusted):
581+
if sidecars.trusted[i][].index <= sidecars.untrusted[j][].index:
582+
res.add(sidecars.trusted[i])
583+
inc i
584+
else:
585+
res.add(sidecars.untrusted[j])
586+
inc j
587+
while i < len(sidecars.trusted):
588+
res.add(sidecars.trusted[i])
589+
inc i
590+
while j < len(sidecars.untrusted):
591+
res.add(sidecars.untrusted[j])
592+
inc j
593+
res
594+
595+
func toTrustedImport*(
596+
sidecars: DataColumnSidecars): DataColumnSidecarsForImport =
597+
## Only for sidecars built locally or read back from our own database.
598+
DataColumnSidecarsForImport(trusted: sidecars.asTrusted)
599+
547600
const
548601
KZG_COMMITMENTS_GINDEX* = get_generalized_index(
549602
BeaconBlockBody, "blob_kzg_commitments")

beacon_chain/validators/message_router.nim

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ type
6969
fulu.DataColumnSidecars
7070

7171
SomeOptSidecars =
72-
NoSidecars | Opt[BlobSidecars] | Opt[fulu.DataColumnSidecars]
72+
NoSidecars | Opt[BlobSidecars] | Opt[fulu.DataColumnSidecarsForImport]
7373

7474
func isGoodForSending(validationResult: ValidationRes): bool =
7575
# When routing messages from REST, it's possible that these have already
@@ -175,7 +175,8 @@ proc publishSidecars(
175175
router: ref MessageRouter,
176176
_: fulu.SignedBeaconBlock,
177177
cols: fulu.DataColumnSidecars
178-
): Future[Opt[fulu.DataColumnSidecars]] {.async: (raises: [CancelledError]).} =
178+
): Future[Opt[fulu.DataColumnSidecarsForImport]] {.
179+
async: (raises: [CancelledError]).} =
179180
var workers = newSeq[Future[SendResult]](len(cols))
180181

181182
for i, dc in cols:
@@ -194,8 +195,10 @@ proc publishSidecars(
194195
notice "Data column sent",
195196
data_column = shortLog(cols[i][])
196197

198+
# Assembled locally, so they need no KZG check on the way back in.
197199
Opt.some(cols.filterIt(
198-
it[].index in router[].processor.fuluColumnQuarantine[].custodyMap))
200+
it[].index in router[].processor.fuluColumnQuarantine[].custodyMap
201+
).toTrustedImport())
199202

200203
proc publishSidecars(
201204
router: ref MessageRouter,

tests/test_block_processor.nim

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,7 @@ suite "Block processor" & preset():
339339
withState(state[]):
340340
let b0 = addTestEngineBlock(cfg, consensusFork, forkyState, cache)
341341
when consensusFork == ConsensusFork.Fulu:
342-
let sidecarsOpt = Opt.none(fulu.DataColumnSidecars)
342+
let sidecarsOpt = Opt.none(fulu.DataColumnSidecarsForImport)
343343
else:
344344
let sidecarsOpt = noSidecars
345345
check (await processor.addBlock(
@@ -427,7 +427,9 @@ suite "Block processor" & preset():
427427
let res = await processor.addBlock(
428428
MsgSource.gossip,
429429
engineBlock.blck,
430-
Opt.some(dataColumnSidecars)
430+
# Untrusted, so this also exercises the KZG check in `storeBlock`
431+
Opt.some(fulu.DataColumnSidecarsForImport(
432+
untrusted: dataColumnSidecars))
431433
)
432434

433435
check:
@@ -460,7 +462,7 @@ suite "Block processor" & preset():
460462
let res = await processor.addBlock(
461463
MsgSource.gossip,
462464
engineBlock.blck,
463-
Opt.none(fulu.DataColumnSidecars)
465+
Opt.none(fulu.DataColumnSidecarsForImport)
464466
)
465467

466468
check:

0 commit comments

Comments
 (0)