verify that state_sim is justifying and finalizing; fix 3 more warnings; rename crosslink_committee_cache to beacon_committee_cache; fix O(n^2) usage of get_base_reward(...)

This commit is contained in:
Dustin Brody 2019-11-15 23:37:39 +01:00
parent 3744ba3a66
commit 6f87c8fd89
11 changed files with 71 additions and 51 deletions

View File

@ -60,8 +60,8 @@ task test, "Run all tests":
buildBinary "all_fixtures_require_ssz", "tests/official/", "-r -d:release -d:chronicles_log_level=DEBUG -d:const_preset=minimal"
buildBinary "all_fixtures_require_ssz", "tests/official/", "-r -d:release -d:chronicles_log_level=DEBUG -d:const_preset=mainnet"
# State sim; getting into 3rd epoch useful
buildBinary "state_sim", "research/", "-r -d:release", "--validators=128 --slots=24"
# State sim; getting into 4th epoch useful to trigger consensus checks
buildBinary "state_sim", "research/", "-r -d:release", "--validators=128 --slots=40"
task sync_lfs_tests, "Sync LFS json tests":
# Syncs the json test files (but not the EF yaml tests)

View File

@ -174,7 +174,7 @@ type
slots*: uint64 # number of slots that are suspected missing
tries*: int
BlockRef* = ref object {.acyclic.}
BlockRef* {.acyclic.} = ref object
## Node in object graph guaranteed to lead back to tail block, and to have
## a corresponding entry in database.
## Block graph should form a tree - in particular, there are no cycles.

View File

@ -197,7 +197,7 @@ else:
proc createEth2Node*(conf: BeaconNodeConf,
bootstrapNodes: seq[BootstrapAddr]): Future[Eth2Node] {.async.} =
var
(extIp, extTcpPort, extUdpPort) = setupNat(conf)
(extIp, extTcpPort, _) = setupNat(conf)
hostAddress = tcpEndPoint(globalListeningAddr, Port conf.tcpPort)
announcedAddresses = if extIp == globalListeningAddr: @[]
else: @[tcpEndPoint(extIp, extTcpPort)]

View File

@ -303,7 +303,7 @@ type
root*: Eth2Digest # hash_tree_root (not signing_root!)
StateCache* = object
crosslink_committee_cache*:
beacon_committee_cache*:
Table[tuple[a: int, b: Eth2Digest], seq[ValidatorIndex]]
active_validator_indices_cache*:
Table[Epoch, seq[ValidatorIndex]]

View File

@ -232,10 +232,11 @@ proc process_justification_and_finalization*(
cat = "finalization"
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#rewards-and-penalties-1
func get_base_reward(state: BeaconState, index: ValidatorIndex): Gwei =
let
total_balance = get_total_active_balance(state)
effective_balance = state.validators[index].effective_balance
func get_base_reward(state: BeaconState, index: ValidatorIndex,
total_balance: auto): Gwei =
# Spec function recalculates total_balance every time, which creates an
# O(n^2) situation.
let effective_balance = state.validators[index].effective_balance
effective_balance * BASE_REWARD_FACTOR div
integer_squareroot(total_balance) div BASE_REWARDS_PER_EPOCH
@ -274,9 +275,10 @@ func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):
for index in eligible_validator_indices:
if index in unslashed_attesting_indices:
rewards[index] +=
get_base_reward(state, index) * attesting_balance div total_balance
get_base_reward(state, index, total_balance) * attesting_balance div
total_balance
else:
penalties[index] += get_base_reward(state, index)
penalties[index] += get_base_reward(state, index, total_balance)
# Proposer and inclusion delay micro-rewards
## This depends on matching_source_attestations being an indexable seq, not a
@ -309,11 +311,12 @@ func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):
if a.inclusion_delay < attestation.inclusion_delay:
attestation = a
let proposer_reward =
(get_base_reward(state, index) div PROPOSER_REWARD_QUOTIENT).Gwei
let
base_reward = get_base_reward(state, index, total_balance)
proposer_reward = (base_reward div PROPOSER_REWARD_QUOTIENT).Gwei
rewards[attestation.proposer_index.int] += proposer_reward
let max_attester_reward = get_base_reward(state, index) - proposer_reward
let max_attester_reward = base_reward - proposer_reward
rewards[index] += max_attester_reward div attestation.inclusion_delay
@ -325,7 +328,7 @@ func get_attestation_deltas(state: BeaconState, stateCache: var StateCache):
state, matching_target_attestations, stateCache)
for index in eligible_validator_indices:
penalties[index] +=
BASE_REWARDS_PER_EPOCH.uint64 * get_base_reward(state, index)
BASE_REWARDS_PER_EPOCH.uint64 * get_base_reward(state, index, total_balance)
if index notin matching_target_attesting_indices:
penalties[index] +=
state.validators[index].effective_balance *
@ -426,9 +429,7 @@ proc process_epoch*(state: var BeaconState) =
## Caching here for get_beacon_committee(...) can break otherwise, since
## get_active_validator_indices(...) usually changes.
# TODO is this cache still necessary/useful? presumably not, but can't remove
# quite yet
clear(per_epoch_cache.crosslink_committee_cache)
clear(per_epoch_cache.beacon_committee_cache)
# @process_reveal_deadlines
# @process_challenge_deadlines

View File

@ -98,8 +98,8 @@ func compute_committee(indices: seq[ValidatorIndex], seed: Eth2Digest,
endIdx = (len(indices).uint64 * (index + 1)) div count
key = (indices.len, seed)
if key notin stateCache.crosslink_committee_cache:
stateCache.crosslink_committee_cache[key] =
if key notin stateCache.beacon_committee_cache:
stateCache.beacon_committee_cache[key] =
get_shuffled_seq(seed, len(indices).uint64)
# These assertions from compute_shuffled_index(...)
@ -110,15 +110,13 @@ func compute_committee(indices: seq[ValidatorIndex], seed: Eth2Digest,
# In spec, this calls get_shuffled_index() every time, but that's wasteful
mapIt(
start.int .. (endIdx.int-1),
indices[stateCache.crosslink_committee_cache[key][it]])
indices[stateCache.beacon_committee_cache[key][it]])
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#get_beacon_committee
func get_beacon_committee*(state: BeaconState, slot: Slot, index: uint64, cache: var StateCache): seq[ValidatorIndex] =
# Return the beacon committee at ``slot`` for ``index``.
let
epoch = compute_epoch_at_slot(slot)
# TODO use state caching for this or not?
committees_per_slot = get_committee_count_at_slot(state, slot)
## This is a somewhat more fragile, but high-ROI, caching setup --
## get_active_validator_indices() is slow to run in a loop and only
@ -127,22 +125,22 @@ func get_beacon_committee*(state: BeaconState, slot: Slot, index: uint64, cache:
cache.active_validator_indices_cache[epoch] =
get_active_validator_indices(state, epoch)
# TODO remove or replace this...
#if epoch notin cache.committee_count_cache:
# cache.committee_count_cache[epoch] = get_committee_count(state, epoch)
# Constant throughout an epoch
if epoch notin cache.committee_count_cache:
cache.committee_count_cache[epoch] =
get_committee_count_at_slot(state, slot)
# TODO profiling & make sure caches populated
compute_committee(
cache.active_validator_indices_cache[epoch],
get_seed(state, epoch, DOMAIN_BEACON_ATTESTER),
(slot mod SLOTS_PER_EPOCH) * committees_per_slot + index,
committees_per_slot * SLOTS_PER_EPOCH,
(slot mod SLOTS_PER_EPOCH) * cache.committee_count_cache[epoch] + index,
cache.committee_count_cache[epoch] * SLOTS_PER_EPOCH,
cache
)
# Not from spec
func get_empty_per_epoch_cache*(): StateCache =
result.crosslink_committee_cache =
result.beacon_committee_cache =
initTable[tuple[a: int, b: Eth2Digest], seq[ValidatorIndex]]()
result.active_validator_indices_cache =
initTable[Epoch, seq[ValidatorIndex]]()

View File

@ -52,7 +52,7 @@ func toSlot*(t: BeaconTime): tuple[afterGenesis: bool, slot: Slot] =
(false, Slot(uint64(-ti) div SECONDS_PER_SLOT))
func toBeaconTime*(c: BeaconClock, t: Time): BeaconTime =
BeaconTime(times.seconds(t - c.genesis))
BeaconTime(times.inSeconds(t - c.genesis))
func toSlot*(c: BeaconClock, t: Time): tuple[afterGenesis: bool, slot: Slot] =
c.toBeaconTime(t).toSlot()

View File

@ -37,8 +37,24 @@ proc writeJson*(prefix, slot, v: auto) =
let fileName = fmt"{prefix:04}-{shortLog(slot):08}.json"
Json.saveFile(fileName, v, pretty = true)
cli do(slots = 448'u,
validators = SLOTS_PER_EPOCH * 9, # One per shard is minimum
func verifyConsensus(state: BeaconState, attesterRatio: auto) =
if attesterRatio < 0.63:
doAssert state.current_justified_checkpoint.epoch == 0
doAssert state.finalized_checkpoint.epoch == 0
# Quorum is 2/3 of validators, and at low numbers, quantization effects
# can dominate, so allow for play above/below attesterRatio of 2/3.
if attesterRatio < 0.72:
return
let current_epoch = get_current_epoch(state)
if current_epoch >= 3:
doAssert state.current_justified_checkpoint.epoch + 1 >= current_epoch
if current_epoch >= 4:
doAssert state.finalized_checkpoint.epoch + 2 >= current_epoch
cli do(slots = SLOTS_PER_EPOCH * 6,
validators = SLOTS_PER_EPOCH * 11, # One per shard is minimum
json_interval = SLOTS_PER_EPOCH,
prefix = 0,
attesterRatio {.desc: "ratio of validators that attest in each round"} = 0.75,
@ -69,6 +85,7 @@ cli do(slots = 448'u,
for i in 0..<slots:
maybeWrite()
verifyConsensus(state, attesterRatio)
let
attestations_idx = state.slot
@ -113,11 +130,13 @@ cli do(slots = 448'u,
for v in scas:
if (rand(r, high(int)).float * attesterRatio).int <= high(int):
if first:
attestation = makeAttestation(state, latest_block_root, v, flags)
attestation =
makeAttestation(state, latest_block_root, v, cache, flags)
first = false
else:
attestation.combine(
makeAttestation(state, latest_block_root, v, flags), flags)
makeAttestation(state, latest_block_root, v, cache, flags),
flags)
if not first:
# add the attestation if any of the validators attested, as given

View File

@ -45,7 +45,7 @@ suite "Attestation pool processing" & preset():
beacon_committee = get_beacon_committee(state.data.data,
state.data.data.slot, 0, cache)
attestation = makeAttestation(
state.data.data, state.blck.root, beacon_committee[0])
state.data.data, state.blck.root, beacon_committee[0], cache)
pool.add(state.data.data, state.blck, attestation)
@ -65,7 +65,7 @@ suite "Attestation pool processing" & preset():
bc0 = get_beacon_committee(state.data.data,
state.data.data.slot, 0, cache)
attestation0 = makeAttestation(
state.data.data, state.blck.root, bc0[0])
state.data.data, state.blck.root, bc0[0], cache)
process_slots(state.data, state.data.data.slot + 1)
@ -73,7 +73,7 @@ suite "Attestation pool processing" & preset():
bc1 = get_beacon_committee(state.data.data,
state.data.data.slot, 0, cache)
attestation1 = makeAttestation(
state.data.data, state.blck.root, bc1[0])
state.data.data, state.blck.root, bc1[0], cache)
# test reverse order
pool.add(state.data.data, state.blck, attestation1)
@ -95,9 +95,9 @@ suite "Attestation pool processing" & preset():
bc0 = get_beacon_committee(state.data.data,
state.data.data.slot, 0, cache)
attestation0 = makeAttestation(
state.data.data, state.blck.root, bc0[0])
state.data.data, state.blck.root, bc0[0], cache)
attestation1 = makeAttestation(
state.data.data, state.blck.root, bc0[1])
state.data.data, state.blck.root, bc0[1], cache)
pool.add(state.data.data, state.blck, attestation0)
pool.add(state.data.data, state.blck, attestation1)
@ -119,9 +119,9 @@ suite "Attestation pool processing" & preset():
bc0 = get_beacon_committee(state.data.data,
state.data.data.slot, 0, cache)
attestation0 = makeAttestation(
state.data.data, state.blck.root, bc0[0])
state.data.data, state.blck.root, bc0[0], cache)
attestation1 = makeAttestation(
state.data.data, state.blck.root, bc0[1])
state.data.data, state.blck.root, bc0[1], cache)
attestation0.combine(attestation1, {skipValidation})
@ -144,9 +144,9 @@ suite "Attestation pool processing" & preset():
bc0 = get_beacon_committee(state.data.data,
state.data.data.slot, 0, cache)
attestation0 = makeAttestation(
state.data.data, state.blck.root, bc0[0])
state.data.data, state.blck.root, bc0[0], cache)
attestation1 = makeAttestation(
state.data.data, state.blck.root, bc0[1])
state.data.data, state.blck.root, bc0[1], cache)
attestation0.combine(attestation1, {skipValidation})

View File

@ -85,10 +85,10 @@ suite "Block processing" & preset():
let
# Create an attestation for slot 1 signed by the only attester we have!
crosslink_committee =
beacon_committee =
get_beacon_committee(state, state.slot, 0, cache)
attestation = makeAttestation(
state, previous_block_root, crosslink_committee[0])
state, previous_block_root, beacon_committee[0], cache)
# Some time needs to pass before attestations are included - this is
# to let the attestation propagate properly to interested participants

View File

@ -158,9 +158,9 @@ proc makeBlock*(
addBlock(next_state, previous_block_root, body)
proc find_beacon_committee(
state: BeaconState, validator_index: ValidatorIndex): auto =
state: BeaconState, validator_index: ValidatorIndex,
cache: var StateCache): auto =
let epoch = compute_epoch_at_slot(state.slot)
var cache = get_empty_per_epoch_cache()
for epoch_committee_index in 0'u64 ..< get_committee_count_at_slot(
state, epoch.compute_start_slot_at_epoch) * SLOTS_PER_EPOCH:
let
@ -174,14 +174,16 @@ proc find_beacon_committee(
proc makeAttestation*(
state: BeaconState, beacon_block_root: Eth2Digest,
validator_index: ValidatorIndex, flags: UpdateFlags = {}): Attestation =
validator_index: ValidatorIndex, cache: var StateCache,
flags: UpdateFlags = {}): Attestation =
let
(committee, slot, index) = find_beacon_committee(state, validator_index)
(committee, slot, index) =
find_beacon_committee(state, validator_index, cache)
validator = state.validators[validator_index]
sac_index = committee.find(validator_index)
data = makeAttestationData(state, slot, index, beacon_block_root)
doAssert sac_index != -1, "find_shard_committee should guarantee this"
doAssert sac_index != -1, "find_beacon_committee should guarantee this"
var aggregation_bits = CommitteeValidatorsBits.init(committee.len)
aggregation_bits.raiseBit sac_index