add tests for unviable blocks (#1271)

* add tests for unviable blocks

also enable finalization tests in all test configs - they're plenty fast
now
also fix newClone for non-rvo cases. sigh.

* fixes
This commit is contained in:
Jacek Sieka 2020-07-01 19:00:14 +02:00 committed by GitHub
parent 66c230ffd1
commit f3e92762e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 90 deletions

View File

@ -50,6 +50,12 @@ OK: 7/7 Fail: 0/7 Skip: 0/7
+ Passes through epoch update, no block [Preset: mainnet] OK
```
OK: 5/5 Fail: 0/5 Skip: 0/5
## BlockPool finalization tests [Preset: mainnet]
```diff
+ init with gaps [Preset: mainnet] OK
+ prune heads on finalization [Preset: mainnet] OK
```
OK: 2/2 Fail: 0/2 Skip: 0/2
## BlockRef and helpers [Preset: mainnet]
```diff
+ getAncestorAt sanity [Preset: mainnet] OK
@ -259,4 +265,4 @@ OK: 8/8 Fail: 0/8 Skip: 0/8
OK: 1/1 Fail: 0/1 Skip: 0/1
---TOTAL---
OK: 158/161 Fail: 0/161 Skip: 3/161
OK: 160/163 Fail: 0/163 Skip: 3/163

View File

@ -607,10 +607,19 @@ template writeValue*(writer: var JsonWriter, value: BitList) =
template newClone*[T: not ref](x: T): ref T =
# TODO not nil in return type: https://github.com/nim-lang/Nim/issues/14146
# TODO use only when x is a function call that returns a new instance!
let res = new typeof(x) # TODO safe to do noinit here?
res[] = x
res
template assignClone*[T: not ref](x: T): ref T =
# This is a bit of a mess: if x is an rvalue (temporary), RVO kicks in for
# newClone - if it's not, `genericAssign` will be called which is ridiculously
# slow - so `assignClone` should be used when RVO doesn't work. sigh.
let res = new typeof(x) # TODO safe to do noinit here?
assign(res[], x)
res
template newClone*[T](x: ref T not nil): ref T =
newClone(x[])

View File

@ -61,7 +61,7 @@ cli do(slots = SLOTS_PER_EPOCH * 6,
attesters: RunningStat
r = initRand(1)
let replayState = newClone(blockPool.headState)
let replayState = assignClone(blockPool.headState)
proc handleAttestations(slot: Slot) =
let

View File

@ -33,7 +33,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
# TODO: BLS signature
timedTest "Deposit " & name & " MAX_EFFECTIVE_BALANCE balance (" &
$(MAX_EFFECTIVE_BALANCE div 10'u64^9) & " ETH)":
var state = newClone(genesisState[])
var state = assignClone(genesisState[])
# Test configuration
# ----------------------------------------
@ -74,7 +74,7 @@ suiteReport "[Unit - Spec - Block processing] Deposits " & preset():
valid_deposit(MAX_EFFECTIVE_BALANCE + 1, "over")
timedTest "Validator top-up":
var state = newClone(genesisState)
var state = assignClone(genesisState[])
# Test configuration
# ----------------------------------------

View File

@ -221,7 +221,7 @@ proc payload =
doAssert genesisState.data.validators.len == int NumValidators
setup:
var state = newClone(genesisState[])
var state = assignClone(genesisState[])
timedTest " Rule I - 234 finalization with enough support":
finalizeOn234(state[], Epoch 5, sufficient_support = true)

View File

@ -10,7 +10,7 @@
import
options, sequtils, unittest,
./testutil, ./testblockutil,
../beacon_chain/spec/[datatypes, digest, validator, state_transition],
../beacon_chain/spec/[datatypes, digest, helpers, validator, state_transition],
../beacon_chain/[beacon_node_types, block_pool, ssz]
when isMainModule:
@ -240,7 +240,7 @@ suiteReport "Block pool processing" & preset():
bs1_3 = b1Add.atSlot(3.Slot)
bs2_3 = b2Add.atSlot(3.Slot)
var tmpState = newClone(pool.headState)
var tmpState = assignClone(pool.headState)
# move to specific block
pool.updateStateData(tmpState[], bs1)
@ -280,94 +280,103 @@ suiteReport "Block pool processing" & preset():
tmpState.blck == b1Add.parent
tmpState.data.data.slot == bs1.parent.slot
when const_preset == "minimal": # These require some minutes in mainnet
import ../beacon_chain/spec/helpers
suiteReport "BlockPool finalization tests" & preset():
setup:
var
db = makeTestDB(SLOTS_PER_EPOCH)
pool = BlockPool.init(db)
cache = get_empty_per_epoch_cache()
suiteReport "BlockPool finalization tests" & preset():
setup:
timedTest "prune heads on finalization" & preset():
# Create a fork that will not be taken
var
blck = makeTestBlock(pool.headState.data, pool.head.blck.root, cache)
tmpState = assignClone(pool.headState.data)
check:
process_slots(
tmpState[], tmpState.data.slot + (5 * SLOTS_PER_EPOCH).uint64)
let lateBlock = makeTestBlock(tmpState[], pool.head.blck.root, cache)
check: pool.add(hash_tree_root(blck.message), blck).isOk
for i in 0 ..< (SLOTS_PER_EPOCH * 6):
if i == 1:
# There are 2 heads now because of the fork at slot 1
check:
pool.tail.children.len == 2
pool.heads.len == 2
var
db = makeTestDB(SLOTS_PER_EPOCH)
pool = BlockPool.init(db)
cache = get_empty_per_epoch_cache()
timedTest "prune heads on finalization" & preset():
block:
# Create a fork that will not be taken
var
blck = makeTestBlock(pool.headState.data, pool.head.blck.root, cache)
check: pool.add(hash_tree_root(blck.message), blck).isOk
for i in 0 ..< (SLOTS_PER_EPOCH * 6):
if i == 1:
# There are 2 heads now because of the fork at slot 1
check:
pool.tail.children.len == 2
pool.heads.len == 2
var
blck = makeTestBlock(
pool.headState.data, pool.head.blck.root, cache,
attestations = makeFullAttestations(
pool.headState.data.data, pool.head.blck.root,
pool.headState.data.data.slot, cache, {}))
let added = pool.add(hash_tree_root(blck.message), blck)[]
pool.updateHead(added)
check:
pool.heads.len() == 1
pool.head.justified.slot.compute_epoch_at_slot() == 5
pool.tail.children.len == 1
let
pool2 = BlockPool.init(db)
# check that the state reloaded from database resembles what we had before
check:
pool2.tail.root == pool.tail.root
pool2.head.blck.root == pool.head.blck.root
pool2.finalizedHead.blck.root == pool.finalizedHead.blck.root
pool2.finalizedHead.slot == pool.finalizedHead.slot
hash_tree_root(pool2.headState.data.data) ==
hash_tree_root(pool.headState.data.data)
hash_tree_root(pool2.justifiedState.data.data) ==
hash_tree_root(pool.justifiedState.data.data)
timedTest "init with gaps" & preset():
var cache = get_empty_per_epoch_cache()
for i in 0 ..< (SLOTS_PER_EPOCH * 6 - 2):
var
blck = makeTestBlock(
pool.headState.data, pool.head.blck.root, cache,
attestations = makeFullAttestations(
pool.headState.data.data, pool.head.blck.root,
pool.headState.data.data.slot, cache, {}))
let added = pool.add(hash_tree_root(blck.message), blck)[]
pool.updateHead(added)
# Advance past epoch so that the epoch transition is gapped
check:
process_slots(
pool.headState.data, Slot(SLOTS_PER_EPOCH * 6 + 2) )
var blck = makeTestBlock(
pool.headState.data, pool.head.blck.root, cache,
attestations = makeFullAttestations(
pool.headState.data.data, pool.head.blck.root,
pool.headState.data.data.slot, cache, {}))
blck = makeTestBlock(
pool.headState.data, pool.head.blck.root, cache,
attestations = makeFullAttestations(
pool.headState.data.data, pool.head.blck.root,
pool.headState.data.data.slot, cache, {}))
let added = pool.add(hash_tree_root(blck.message), blck)[]
pool.updateHead(added)
let
pool2 = BlockPool.init(db)
check:
pool.heads.len() == 1
pool.head.justified.slot.compute_epoch_at_slot() == 5
pool.tail.children.len == 1
# check that the state reloaded from database resembles what we had before
check:
pool2.tail.root == pool.tail.root
pool2.head.blck.root == pool.head.blck.root
pool2.finalizedHead.blck.root == pool.finalizedHead.blck.root
pool2.finalizedHead.slot == pool.finalizedHead.slot
hash_tree_root(pool2.headState.data.data) ==
hash_tree_root(pool.headState.data.data)
hash_tree_root(pool2.justifiedState.data.data) ==
hash_tree_root(pool.justifiedState.data.data)
check:
# The late block is a block whose parent was finalized long ago and thus
# is no longer a viable head candidate
pool.add(hash_tree_root(lateBlock.message), lateBlock).error == Unviable
let
pool2 = BlockPool.init(db)
# check that the state reloaded from database resembles what we had before
check:
pool2.tail.root == pool.tail.root
pool2.head.blck.root == pool.head.blck.root
pool2.finalizedHead.blck.root == pool.finalizedHead.blck.root
pool2.finalizedHead.slot == pool.finalizedHead.slot
hash_tree_root(pool2.headState.data.data) ==
hash_tree_root(pool.headState.data.data)
hash_tree_root(pool2.justifiedState.data.data) ==
hash_tree_root(pool.justifiedState.data.data)
timedTest "init with gaps" & preset():
var cache = get_empty_per_epoch_cache()
for i in 0 ..< (SLOTS_PER_EPOCH * 6 - 2):
var
blck = makeTestBlock(
pool.headState.data, pool.head.blck.root, cache,
attestations = makeFullAttestations(
pool.headState.data.data, pool.head.blck.root,
pool.headState.data.data.slot, cache, {}))
let added = pool.add(hash_tree_root(blck.message), blck)[]
pool.updateHead(added)
# Advance past epoch so that the epoch transition is gapped
check:
process_slots(
pool.headState.data, Slot(SLOTS_PER_EPOCH * 6 + 2) )
var blck = makeTestBlock(
pool.headState.data, pool.head.blck.root, cache,
attestations = makeFullAttestations(
pool.headState.data.data, pool.head.blck.root,
pool.headState.data.data.slot, cache, {}))
let added = pool.add(hash_tree_root(blck.message), blck)[]
pool.updateHead(added)
let
pool2 = BlockPool.init(db)
# check that the state reloaded from database resembles what we had before
check:
pool2.tail.root == pool.tail.root
pool2.head.blck.root == pool.head.blck.root
pool2.finalizedHead.blck.root == pool.finalizedHead.blck.root
pool2.finalizedHead.slot == pool.finalizedHead.slot
hash_tree_root(pool2.headState.data.data) ==
hash_tree_root(pool.headState.data.data)
hash_tree_root(pool2.justifiedState.data.data) ==
hash_tree_root(pool.justifiedState.data.data)

View File

@ -143,7 +143,7 @@ proc makeTestBlock*(
# It's a bit awkward - in order to produce a block for N+1, we need to
# calculate what the state will look like after that block has been applied,
# because the block includes the state root.
var tmpState = newClone(state)
var tmpState = assignClone(state)
addTestBlock(
tmpState[], parent_root, cache, eth1_data, attestations, deposits,
graffiti, flags)