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:
parent
3744ba3a66
commit
6f87c8fd89
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]]()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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})
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue