early exit `commonAncestor` when comparing with `finalizedHead` (#5174)
* early exit `commonAncestor` when comparing with `finalizedHead` As all `BlockRef` lead to `finalizedHead` (`parent == nil`), can shortcut in that situation and immediately return `finalizedHead` if passed as one of the arguments. * typo in comment * add test from #5152 Co-authored-by: tersec <tersec@users.noreply.github.com> * add note about test complexity * regenerate test summary --------- Co-authored-by: tersec <tersec@users.noreply.github.com>
This commit is contained in:
parent
7fc99ff040
commit
5115aaedb7
|
@ -447,8 +447,9 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||||
## Shufflings
|
## Shufflings
|
||||||
```diff
|
```diff
|
||||||
+ Accelerated shuffling computation OK
|
+ Accelerated shuffling computation OK
|
||||||
|
+ Accelerated shuffling computation (with epochRefState jump) OK
|
||||||
```
|
```
|
||||||
OK: 1/1 Fail: 0/1 Skip: 0/1
|
OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||||
## Slashing Interchange tests [Preset: mainnet]
|
## Slashing Interchange tests [Preset: mainnet]
|
||||||
```diff
|
```diff
|
||||||
+ Slashing test: duplicate_pubkey_not_slashable.json OK
|
+ Slashing test: duplicate_pubkey_not_slashable.json OK
|
||||||
|
@ -689,4 +690,4 @@ OK: 2/2 Fail: 0/2 Skip: 0/2
|
||||||
OK: 9/9 Fail: 0/9 Skip: 0/9
|
OK: 9/9 Fail: 0/9 Skip: 0/9
|
||||||
|
|
||||||
---TOTAL---
|
---TOTAL---
|
||||||
OK: 390/395 Fail: 0/395 Skip: 5/395
|
OK: 391/396 Fail: 0/396 Skip: 5/396
|
||||||
|
|
|
@ -162,6 +162,10 @@ func commonAncestor*(a, b: BlockRef, lowSlot: Slot): Opt[BlockRef] =
|
||||||
doAssert b != nil
|
doAssert b != nil
|
||||||
if a.slot < lowSlot or b.slot < lowSlot:
|
if a.slot < lowSlot or b.slot < lowSlot:
|
||||||
return err()
|
return err()
|
||||||
|
if a.parent == nil:
|
||||||
|
return ok a # All `BlockRef` lead to `finalizedHead`
|
||||||
|
if b.parent == nil:
|
||||||
|
return ok b # All `BlockRef` lead to `finalizedHead`
|
||||||
|
|
||||||
var
|
var
|
||||||
aa = a
|
aa = a
|
||||||
|
|
|
@ -1380,11 +1380,7 @@ func ancestorSlotForShuffling*(
|
||||||
|
|
||||||
# Compute ancestor slot for starting RANDAO recovery
|
# Compute ancestor slot for starting RANDAO recovery
|
||||||
let
|
let
|
||||||
ancestorBlck =
|
ancestorBlck = ? commonAncestor(blck, stateBlck, lowSlot)
|
||||||
if stateBlck == dag.finalizedHead.blck:
|
|
||||||
dag.finalizedHead.blck
|
|
||||||
else:
|
|
||||||
? commonAncestor(blck, stateBlck, lowSlot)
|
|
||||||
dependentSlot = epoch.attester_dependent_slot
|
dependentSlot = epoch.attester_dependent_slot
|
||||||
doAssert dependentSlot >= lowSlot
|
doAssert dependentSlot >= lowSlot
|
||||||
ok min(min(stateBid.slot, ancestorBlck.slot), dependentSlot)
|
ok min(min(stateBid.slot, ancestorBlck.slot), dependentSlot)
|
||||||
|
|
|
@ -1274,6 +1274,15 @@ suite "Shufflings":
|
||||||
# Cover entire range of epochs plus some extra
|
# Cover entire range of epochs plus some extra
|
||||||
const maxEpochOfInterest = compute_activation_exit_epoch(11.Epoch) + 2
|
const maxEpochOfInterest = compute_activation_exit_epoch(11.Epoch) + 2
|
||||||
|
|
||||||
|
template checkShuffling(
|
||||||
|
epochRef: Result[EpochRef, cstring],
|
||||||
|
computedShufflingRefParam: Opt[ShufflingRef]) =
|
||||||
|
## Check that computed shuffling matches the one from `EpochRef`.
|
||||||
|
block:
|
||||||
|
let computedShufflingRef = computedShufflingRefParam
|
||||||
|
if computedShufflingRef.isOk:
|
||||||
|
check computedShufflingRef.get[] == epochRef.get.shufflingRef[]
|
||||||
|
|
||||||
test "Accelerated shuffling computation":
|
test "Accelerated shuffling computation":
|
||||||
randomize()
|
randomize()
|
||||||
let forkBlocks = dag.forkBlocks.toSeq()
|
let forkBlocks = dag.forkBlocks.toSeq()
|
||||||
|
@ -1286,16 +1295,11 @@ suite "Shufflings":
|
||||||
let epochRef = dag.getEpochRef(blck, epoch, true)
|
let epochRef = dag.getEpochRef(blck, epoch, true)
|
||||||
check epochRef.isOk
|
check epochRef.isOk
|
||||||
|
|
||||||
proc checkShuffling(computedShufflingRef: Opt[ShufflingRef]) =
|
|
||||||
## Check that computed shuffling matches the one from `EpochRef`.
|
|
||||||
if computedShufflingRef.isOk:
|
|
||||||
check computedShufflingRef.get[] == epochRef.get.shufflingRef[]
|
|
||||||
|
|
||||||
# If shuffling is computable from DAG, check its correctness
|
# If shuffling is computable from DAG, check its correctness
|
||||||
checkShuffling dag.computeShufflingRefFromMemory(blck, epoch)
|
epochRef.checkShuffling dag.computeShufflingRefFromMemory(blck, epoch)
|
||||||
|
|
||||||
# If shuffling is computable from DB, check its correctness
|
# If shuffling is computable from DB, check its correctness
|
||||||
checkShuffling dag.computeShufflingRefFromDatabase(blck, epoch)
|
epochRef.checkShuffling dag.computeShufflingRefFromDatabase(blck, epoch)
|
||||||
|
|
||||||
# Shuffling should be correct when starting from any cached state
|
# Shuffling should be correct when starting from any cached state
|
||||||
for state in states:
|
for state in states:
|
||||||
|
@ -1311,4 +1315,53 @@ suite "Shufflings":
|
||||||
check shufflingRef.isErr
|
check shufflingRef.isErr
|
||||||
else:
|
else:
|
||||||
check shufflingRef.isOk
|
check shufflingRef.isOk
|
||||||
checkShuffling shufflingRef
|
epochRef.checkShuffling shufflingRef
|
||||||
|
|
||||||
|
test "Accelerated shuffling computation (with epochRefState jump)":
|
||||||
|
# Test cases where `epochRefState` is set to a very old block
|
||||||
|
# that is advanced by several epochs to a recent slot.
|
||||||
|
#
|
||||||
|
# This is not dependent on the multilayer branching of the "Shufflings"
|
||||||
|
# suite, but a function of getEpochRef extending epochRefState towards
|
||||||
|
# a slot which it is essentially hallucinating a state, because it is
|
||||||
|
# not accounting for the blocks with deposits. As it takes non-trivial
|
||||||
|
# time to set up the "Shufflings" suite, we reuse its more complex DAG.
|
||||||
|
#
|
||||||
|
# The purely random fuzzing/tests have difficulty triggering this, because
|
||||||
|
# this needs to happen across a wide portion of the sampled range so that:
|
||||||
|
# (1) it checks a maximally early slot, both to create the gaps needed for
|
||||||
|
# (2) and (3), and to keep both blocks on the same forks, with maximal
|
||||||
|
# likelihood;
|
||||||
|
# (2) calls getEpochRef with a late enough epoch to trigger the
|
||||||
|
# hallucination of relevance (>= epoch 4 typically works); and
|
||||||
|
# (3) there then have to be enough slots between the last added block and
|
||||||
|
# the next state which will be sampled so that the validators can get
|
||||||
|
# active, after some spec 5 epoch delay. This pushes the lowest epoch
|
||||||
|
# possible to not much less than 8 which is already near the high end
|
||||||
|
# of the epoch sampling. Too early an epoch and it is within range of
|
||||||
|
# the headState check which gets it first, so the epochStateRef isn't
|
||||||
|
# exercised.
|
||||||
|
|
||||||
|
let forkBlocks = dag.forkBlocks.toSeq()
|
||||||
|
|
||||||
|
proc findKeyedBlck(m: Slot): int =
|
||||||
|
# Avoid depending on implementation details of how `forkBlocks` is ordered
|
||||||
|
for idx, fb in forkBlocks:
|
||||||
|
if fb.data.slot == m:
|
||||||
|
return idx
|
||||||
|
raiseAssert "Unreachable"
|
||||||
|
|
||||||
|
# The epoch for the first block can range from at least 4 to 10
|
||||||
|
for (blockIdx, epoch) in [
|
||||||
|
(findKeyedBlck(64.Slot), 10.Epoch),
|
||||||
|
(findKeyedBlck(255.Slot), 8.Epoch)]:
|
||||||
|
let
|
||||||
|
blck = forkBlocks[blockIdx].data
|
||||||
|
epochRef = dag.getEpochRef(blck, epoch, true)
|
||||||
|
doAssert epochRef.isOk
|
||||||
|
|
||||||
|
# If shuffling is computable from DAG, check its correctness
|
||||||
|
epochRef.checkShuffling dag.computeShufflingRefFromMemory(blck, epoch)
|
||||||
|
|
||||||
|
# If shuffling is computable from DB, check its correctness
|
||||||
|
epochRef.checkShuffling dag.computeShufflingRefFromDatabase(blck, epoch)
|
||||||
|
|
Loading…
Reference in New Issue