Skip to content

Commit de1060e

Browse files
authored
centralize p2p validation in a single file and address #2377 (comment) (#2383)
1 parent 5f750f8 commit de1060e

File tree

13 files changed

+323
-307
lines changed

13 files changed

+323
-307
lines changed

beacon_chain/consensus_object_pools/attestation_pool.nim

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99

1010
import
1111
# Standard libraries
12-
std/[deques, options, sequtils, tables],
12+
std/[options, tables, sequtils],
1313
# Status libraries
1414
chronicles, stew/[byteutils], json_serialization/std/sets as jsonSets,
1515
# Internal
16-
../spec/[beaconstate, datatypes, crypto, digest, helpers],
16+
../spec/[beaconstate, datatypes, crypto, digest],
1717
../ssz/merkleization,
18-
"."/[spec_cache, blockchain_dag, block_clearance, block_quarantine],
18+
"."/[spec_cache, blockchain_dag, block_quarantine],
1919
../beacon_node_types,
2020
../fork_choice/fork_choice
2121

beacon_chain/consensus_object_pools/block_clearance.nim

Lines changed: 0 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -328,144 +328,3 @@ proc addRawBlock*(
328328
if parent != nil:
329329
return addRawBlockKnownParent(dag, quarantine, signedBlock, parent, onBlockAdded)
330330
return addRawBlockUnresolved(dag, quarantine, signedBlock)
331-
332-
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md#beacon_block
333-
proc isValidBeaconBlock*(
334-
dag: ChainDAGRef, quarantine: var QuarantineRef,
335-
signed_beacon_block: SignedBeaconBlock, wallTime: BeaconTime,
336-
flags: UpdateFlags):
337-
Result[void, (ValidationResult, BlockError)] =
338-
logScope:
339-
topics = "clearance valid_blck"
340-
received_block = shortLog(signed_beacon_block.message)
341-
blockRoot = shortLog(signed_beacon_block.root)
342-
343-
# In general, checks are ordered from cheap to expensive. Especially, crypto
344-
# verification could be quite a bit more expensive than the rest. This is an
345-
# externally easy-to-invoke function by tossing network packets at the node.
346-
347-
# [IGNORE] The block is not from a future slot (with a
348-
# MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance) -- i.e. validate that
349-
# signed_beacon_block.message.slot <= current_slot (a client MAY queue future
350-
# blocks for processing at the appropriate slot).
351-
if not (signed_beacon_block.message.slot <=
352-
(wallTime + MAXIMUM_GOSSIP_CLOCK_DISPARITY).slotOrZero):
353-
debug "block is from a future slot",
354-
wallSlot = wallTime.toSlot()
355-
return err((ValidationResult.Ignore, Invalid))
356-
357-
# [IGNORE] The block is from a slot greater than the latest finalized slot --
358-
# i.e. validate that signed_beacon_block.message.slot >
359-
# compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
360-
if not (signed_beacon_block.message.slot > dag.finalizedHead.slot):
361-
debug "block is not from a slot greater than the latest finalized slot"
362-
return err((ValidationResult.Ignore, Invalid))
363-
364-
# [IGNORE] The block is the first block with valid signature received for the
365-
# proposer for the slot, signed_beacon_block.message.slot.
366-
#
367-
# While this condition is similar to the proposer slashing condition at
368-
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/validator.md#proposer-slashing
369-
# it's not identical, and this check does not address slashing:
370-
#
371-
# (1) The beacon blocks must be conflicting, i.e. different, for the same
372-
# slot and proposer. This check also catches identical blocks.
373-
#
374-
# (2) By this point in the function, it's not been checked whether they're
375-
# signed yet. As in general, expensive checks should be deferred, this
376-
# would add complexity not directly relevant this function.
377-
#
378-
# (3) As evidenced by point (1), the similarity in the validation condition
379-
# and slashing condition, while not coincidental, aren't similar enough
380-
# to combine, as one or the other might drift.
381-
#
382-
# (4) Furthermore, this function, as much as possible, simply returns a yes
383-
# or no answer, without modifying other state for p2p network interface
384-
# validation. Complicating this interface, for the sake of sharing only
385-
# couple lines of code, wouldn't be worthwhile.
386-
#
387-
# TODO might check unresolved/orphaned blocks too, and this might not see all
388-
# blocks at a given slot (though, in theory, those get checked elsewhere), or
389-
# adding metrics that count how often these conditions occur.
390-
let
391-
slotBlockRef = getBlockBySlot(dag, signed_beacon_block.message.slot)
392-
393-
if not slotBlockRef.isNil:
394-
let blck = dag.get(slotBlockRef).data
395-
if blck.message.proposer_index ==
396-
signed_beacon_block.message.proposer_index and
397-
blck.message.slot == signed_beacon_block.message.slot and
398-
blck.signature.toRaw() != signed_beacon_block.signature.toRaw():
399-
notice "block isn't first block with valid signature received for the proposer",
400-
blckRef = slotBlockRef,
401-
existing_block = shortLog(blck.message)
402-
return err((ValidationResult.Ignore, Invalid))
403-
404-
# [IGNORE] The block's parent (defined by block.parent_root) has been seen
405-
# (via both gossip and non-gossip sources) (a client MAY queue blocks for
406-
# processing once the parent block is retrieved).
407-
#
408-
# And implicitly:
409-
# [REJECT] The block's parent (defined by block.parent_root) passes validation.
410-
let parent_ref = dag.getRef(signed_beacon_block.message.parent_root)
411-
if parent_ref.isNil:
412-
# Pending dag gets checked via `ChainDAGRef.add(...)` later, and relevant
413-
# checks are performed there. In usual paths beacon_node adds blocks via
414-
# ChainDAGRef.add(...) directly, with no additional validity checks.
415-
debug "parent unknown, putting block in quarantine",
416-
current_slot = wallTime.toSlot()
417-
if not quarantine.add(dag, signed_beacon_block):
418-
debug "Block quarantine full"
419-
return err((ValidationResult.Ignore, MissingParent))
420-
421-
# [REJECT] The current finalized_checkpoint is an ancestor of block -- i.e.
422-
# get_ancestor(store, block.parent_root,
423-
# compute_start_slot_at_epoch(store.finalized_checkpoint.epoch)) ==
424-
# store.finalized_checkpoint.root
425-
let
426-
finalized_checkpoint = dag.headState.data.data.finalized_checkpoint
427-
ancestor = get_ancestor(
428-
parent_ref, compute_start_slot_at_epoch(finalized_checkpoint.epoch))
429-
430-
if ancestor.isNil:
431-
debug "couldn't find ancestor block"
432-
return err((ValidationResult.Ignore, Invalid)) # might not've received block
433-
434-
if not (finalized_checkpoint.root in [ancestor.root, Eth2Digest()]):
435-
debug "block not descendent of finalized block"
436-
return err((ValidationResult.Reject, Invalid))
437-
438-
# [REJECT] The block is proposed by the expected proposer_index for the
439-
# block's slot in the context of the current shuffling (defined by
440-
# parent_root/slot). If the proposer_index cannot immediately be verified
441-
# against the expected shuffling, the block MAY be queued for later
442-
# processing while proposers for the block's branch are calculated -- in such
443-
# a case do not REJECT, instead IGNORE this message.
444-
let
445-
proposer = getProposer(dag, parent_ref, signed_beacon_block.message.slot)
446-
447-
if proposer.isNone:
448-
warn "cannot compute proposer for message"
449-
return err((ValidationResult.Ignore, Invalid)) # internal issue
450-
451-
if proposer.get()[0] !=
452-
ValidatorIndex(signed_beacon_block.message.proposer_index):
453-
notice "block had unexpected proposer",
454-
expected_proposer = proposer.get()[0]
455-
return err((ValidationResult.Reject, Invalid))
456-
457-
# [REJECT] The proposer signature, signed_beacon_block.signature, is valid
458-
# with respect to the proposer_index pubkey.
459-
if not verify_block_signature(
460-
dag.headState.data.data.fork,
461-
dag.headState.data.data.genesis_validators_root,
462-
signed_beacon_block.message.slot,
463-
signed_beacon_block.message,
464-
proposer.get()[1],
465-
signed_beacon_block.signature):
466-
debug "block failed signature verification",
467-
signature = shortLog(signed_beacon_block.signature)
468-
469-
return err((ValidationResult.Reject, Invalid))
470-
471-
ok()

beacon_chain/consensus_object_pools/exit_pool.nim

Lines changed: 7 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,22 @@
99

1010
import
1111
# Standard libraries
12-
std/[deques, intsets, options, sequtils, tables],
12+
std/[deques, intsets, tables],
1313
# Status libraries
14-
chronicles, json_serialization/std/sets as jsonSets,
14+
chronicles,
1515
# Internal
16-
../spec/[crypto, datatypes, helpers, state_transition_block],
17-
"."/[blockchain_dag, block_clearance, block_quarantine],
16+
../spec/[crypto, datatypes, helpers],
17+
"."/[blockchain_dag, block_quarantine],
1818
../beacon_node_types
1919

2020
export beacon_node_types, intsets
2121

2222
logScope: topics = "exitpool"
2323

2424
const
25-
ATTESTER_SLASHINGS_BOUND = MAX_ATTESTER_SLASHINGS * 2
26-
PROPOSER_SLASHINGS_BOUND = MAX_PROPOSER_SLASHINGS * 2
27-
VOLUNTARY_EXITS_BOUND = MAX_VOLUNTARY_EXITS * 2
25+
ATTESTER_SLASHINGS_BOUND* = MAX_ATTESTER_SLASHINGS * 2
26+
PROPOSER_SLASHINGS_BOUND* = MAX_PROPOSER_SLASHINGS * 2
27+
VOLUNTARY_EXITS_BOUND* = MAX_VOLUNTARY_EXITS * 2
2828

2929
proc init*(
3030
T: type ExitPool, chainDag: ChainDAGRef, quarantine: QuarantineRef): T =
@@ -138,105 +138,3 @@ func getVoluntaryExitsForBlock*(pool: var ExitPool):
138138
## Retrieve voluntary exits that may be added to a new block
139139
getExitMessagesForBlock[SignedVoluntaryExit](
140140
pool.voluntary_exits, pool, MAX_VOLUNTARY_EXITS)
141-
142-
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md#attester_slashing
143-
proc validateAttesterSlashing*(
144-
pool: var ExitPool, attester_slashing: AttesterSlashing):
145-
Result[bool, (ValidationResult, cstring)] =
146-
# [IGNORE] At least one index in the intersection of the attesting indices of
147-
# each attestation has not yet been seen in any prior attester_slashing (i.e.
148-
# attester_slashed_indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices),
149-
# verify if any(attester_slashed_indices.difference(prior_seen_attester_slashed_indices))).
150-
# TODO sequtils2 should be able to make this more reasonable, from asSeq on
151-
# down, and can sort and just find intersection that way
152-
let
153-
attestation_1_indices =
154-
attester_slashing.attestation_1.attesting_indices.asSeq
155-
attestation_2_indices =
156-
attester_slashing.attestation_2.attesting_indices.asSeq
157-
attester_slashed_indices =
158-
toIntSet(attestation_1_indices) * toIntSet(attestation_2_indices)
159-
160-
if not disjoint(
161-
attester_slashed_indices, pool.prior_seen_attester_slashed_indices):
162-
return err((ValidationResult.Ignore, cstring(
163-
"validateAttesterSlashing: attester-slashed index already attester-slashed")))
164-
165-
# [REJECT] All of the conditions within process_attester_slashing pass
166-
# validation.
167-
let attester_slashing_validity =
168-
check_attester_slashing(
169-
pool.chainDag.headState.data.data, attester_slashing, {})
170-
if attester_slashing_validity.isErr:
171-
return err((ValidationResult.Reject, attester_slashing_validity.error))
172-
173-
pool.prior_seen_attester_slashed_indices.incl attester_slashed_indices
174-
pool.attester_slashings.addExitMessage(
175-
attester_slashing, ATTESTER_SLASHINGS_BOUND)
176-
177-
ok(true)
178-
179-
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md#proposer_slashing
180-
proc validateProposerSlashing*(
181-
pool: var ExitPool, proposer_slashing: ProposerSlashing):
182-
Result[bool, (ValidationResult, cstring)] =
183-
# Not from spec; the rest of NBC wouldn't have correctly processed it either.
184-
if proposer_slashing.signed_header_1.message.proposer_index > high(int).uint64:
185-
return err((ValidationResult.Ignore, cstring(
186-
"validateProposerSlashing: proposer-slashed index too high")))
187-
188-
# [IGNORE] The proposer slashing is the first valid proposer slashing
189-
# received for the proposer with index
190-
# proposer_slashing.signed_header_1.message.proposer_index.
191-
if proposer_slashing.signed_header_1.message.proposer_index.int in
192-
pool.prior_seen_proposer_slashed_indices:
193-
return err((ValidationResult.Ignore, cstring(
194-
"validateProposerSlashing: proposer-slashed index already proposer-slashed")))
195-
196-
# [REJECT] All of the conditions within process_proposer_slashing pass validation.
197-
let proposer_slashing_validity =
198-
check_proposer_slashing(
199-
pool.chainDag.headState.data.data, proposer_slashing, {})
200-
if proposer_slashing_validity.isErr:
201-
return err((ValidationResult.Reject, proposer_slashing_validity.error))
202-
203-
pool.prior_seen_proposer_slashed_indices.incl(
204-
proposer_slashing.signed_header_1.message.proposer_index.int)
205-
pool.proposer_slashings.addExitMessage(
206-
proposer_slashing, PROPOSER_SLASHINGS_BOUND)
207-
208-
ok(true)
209-
210-
# https://github.com/ethereum/eth2.0-specs/blob/v1.0.1/specs/phase0/p2p-interface.md#voluntary_exit
211-
proc validateVoluntaryExit*(
212-
pool: var ExitPool, signed_voluntary_exit: SignedVoluntaryExit):
213-
Result[void, (ValidationResult, cstring)] =
214-
# [IGNORE] The voluntary exit is the first valid voluntary exit received for
215-
# the validator with index signed_voluntary_exit.message.validator_index.
216-
if signed_voluntary_exit.message.validator_index >=
217-
pool.chainDag.headState.data.data.validators.lenu64:
218-
return err((ValidationResult.Ignore, cstring(
219-
"validateVoluntaryExit: validator index too high")))
220-
221-
# Since pool.chainDag.headState.data.data.validators is a seq, this means
222-
# signed_voluntary_exit.message.validator_index.int is already valid, but
223-
# check explicitly if one changes that data structure.
224-
if signed_voluntary_exit.message.validator_index.int in
225-
pool.prior_seen_voluntary_exit_indices:
226-
return err((ValidationResult.Ignore, cstring(
227-
"validateVoluntaryExit: validator index already voluntarily exited")))
228-
229-
# [REJECT] All of the conditions within process_voluntary_exit pass
230-
# validation.
231-
let voluntary_exit_validity =
232-
check_voluntary_exit(
233-
pool.chainDag.headState.data.data, signed_voluntary_exit, {})
234-
if voluntary_exit_validity.isErr:
235-
return err((ValidationResult.Reject, voluntary_exit_validity.error))
236-
237-
pool.prior_seen_voluntary_exit_indices.incl(
238-
signed_voluntary_exit.message.validator_index.int)
239-
pool.voluntary_exits.addExitMessage(
240-
signed_voluntary_exit, VOLUNTARY_EXITS_BOUND)
241-
242-
ok()

beacon_chain/gossip_processing/eth2_processor.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import
1313
chronicles, chronos, metrics,
1414
../spec/[crypto, datatypes, digest],
1515
../consensus_object_pools/[block_clearance, blockchain_dag, exit_pool, attestation_pool],
16-
./attestation_aggregation,
16+
./gossip_validation,
1717
../validators/validator_pool,
1818
../beacon_node_types,
1919
../beacon_clock, ../conf, ../ssz/sszdump

0 commit comments

Comments
 (0)