More 0.5.1 spec updates (#195)

* rm gone-in-0.5.0 Proposal, verifyBlockSignature, and slot check which moved to spec function processBlockHeader

* mark get_attestation_participants and get_epoch_committee_count as 0.5.1; finish updating processAttestations to 0.5.1; add kludgy workaround for bug relating to get_winning_roots_etc using crosslink_data_root as index when we have that as ZERO_HASH for all leading to it confusing attesters on different shards; rm BeaconState.latest_attestations, which splits into previous_epoch_attestations and current_epoch_attestations

* Fix CI due to removed latest_attestations field
This commit is contained in:
Dustin Brody 2019-03-22 18:33:12 +00:00 committed by GitHub
parent 0a027e410a
commit 9f55b4646c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 114 additions and 135 deletions

View File

@ -428,13 +428,8 @@ proc proposeBlock(node: BeaconNode,
newBlock.state_root = node.state.root
let proposal = Proposal(
slot: slot.uint64,
block_root: Eth2Digest(data: signed_root(newBlock)),
signature: ValidatorSig(),
)
newBlock.signature =
await validator.signBlockProposal(node.state.data.fork, proposal)
await validator.signBlockProposal(node.state.data.fork, newBlock)
# TODO what are we waiting for here? broadcast should never block, and never
# fail...
@ -442,7 +437,7 @@ proc proposeBlock(node: BeaconNode,
info "Block proposed",
blck = shortLog(newBlock),
blockRoot = shortLog(proposal.block_root),
blockRoot = shortLog(Eth2Digest(data: signed_root(newBlock))),
validator = shortValidatorKey(node, validator.idx),
idx = validator.idx

View File

@ -334,7 +334,7 @@ func get_block_root*(state: BeaconState,
doAssert slot < state.slot
state.latest_block_roots[slot mod SLOTS_PER_HISTORICAL_ROOT]
# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#get_attestation_participants
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#get_attestation_participants
func get_attestation_participants*(state: BeaconState,
attestation_data: AttestationData,
bitfield: BitField): seq[ValidatorIndex] =
@ -350,10 +350,10 @@ func get_attestation_participants*(state: BeaconState,
# TODO Linear search through shard list? borderline ok, it's a small list
# TODO iterator candidate
## Return the participant indices at for the ``attestation_data`` and
## ``bitfield``.
# Find the committee in the list with the desired shard
let crosslink_committees = get_crosslink_committees_at_slot(
state, attestation_data.slot)
doAssert anyIt(
crosslink_committees,
it[1] == attestation_data.shard)
@ -442,8 +442,7 @@ func update_validator_registry*(state: var BeaconState) =
state.validator_registry_update_epoch = current_epoch
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#attestations
# with last half or so still not fully converted from 0.4.0 (tag, remove when done)
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#attestations
proc checkAttestation*(
state: var BeaconState, attestation: Attestation, flags: UpdateFlags): bool =
## Check that an attestation follows the rules of being included in the state
@ -499,34 +498,50 @@ proc checkAttestation*(
state_slot = humaneSlotNum(state.slot))
return
if not (state.latest_crosslinks[attestation.data.shard] in [
attestation.data.previous_crosslink,
Crosslink(
crosslink_data_root: attestation.data.crosslink_data_root,
epoch: slot_to_epoch(attestation.data.slot))]):
# Check that the crosslink data is valid
let acceptable_crosslink_data = @[
# Case 1: Latest crosslink matches the one in the state
attestation.data.previous_crosslink,
# Case 2: State has already been updated, state's latest crosslink matches
# the crosslink the attestation is trying to create
Crosslink(
crosslink_data_root: attestation.data.crosslink_data_root,
epoch: slot_to_epoch(attestation.data.slot)
)
]
if not (state.latest_crosslinks[attestation.data.shard] in
acceptable_crosslink_data):
warn("Unexpected crosslink shard",
state_latest_crosslinks_attestation_data_shard =
state.latest_crosslinks[attestation.data.shard],
attestation_data_previous_crosslink = attestation.data.previous_crosslink,
epoch = humaneEpochNum(slot_to_epoch(attestation.data.slot)),
crosslink_data_root = attestation.data.crosslink_data_root)
actual_epoch = slot_to_epoch(attestation.data.slot),
crosslink_data_root = attestation.data.crosslink_data_root,
acceptable_crosslink_data = acceptable_crosslink_data)
return
doAssert allIt(attestation.custody_bitfield.bits, it == 0) #TO BE REMOVED IN PHASE 1
# Attestation must be nonempty!
doAssert anyIt(attestation.aggregation_bitfield.bits, it != 0)
# Custody must be empty (to be removed in phase 1)
doAssert allIt(attestation.custody_bitfield.bits, it == 0)
# Get the committee for the specific shard that this attestation is for
let crosslink_committee = mapIt(
filterIt(get_crosslink_committees_at_slot(state, attestation.data.slot),
it.shard == attestation.data.shard),
it.committee)[0]
# Custody bitfield must be a subset of the attestation bitfield
doAssert allIt(0 ..< len(crosslink_committee),
if not get_bitfield_bit(attestation.aggregation_bitfield, it):
# Should always be true in phase 0, because of above assertion
not get_bitfield_bit(attestation.custody_bitfield, it)
else:
true)
# Verify aggregate signature
let
participants = get_attestation_participants(
state, attestation.data, attestation.aggregation_bitfield)
@ -557,7 +572,7 @@ proc checkAttestation*(
DOMAIN_ATTESTATION),
)
# To be removed in Phase1:
# Crosslink data root is zero (to be removed in phase 1)
if attestation.data.crosslink_data_root != ZERO_HASH:
warn("Invalid crosslink data root")
return
@ -573,7 +588,6 @@ func prepare_validator_for_withdrawal*(state: var BeaconState, index: ValidatorI
validator.withdrawable_epoch = get_current_epoch(state) +
MIN_VALIDATOR_WITHDRAWABILITY_DELAY
# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/validator/0_beacon-chain-validator.md#attestations-1
proc makeAttestationData*(
state: BeaconState, shard: uint64,
beacon_block_root: Eth2Digest): AttestationData =

View File

@ -46,7 +46,7 @@ type
Epoch* = distinct uint64
const
SPEC_VERSION* = "0.5.0" ## \
SPEC_VERSION* = "0.5.1" ## \
## Spec version we're aiming to be compatible with, right now
## TODO: improve this scheme once we can negotiate versions in protocol
@ -360,18 +360,7 @@ type
voluntary_exits*: seq[VoluntaryExit]
transfers*: seq[Transfer]
# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#proposal
Proposal* = object
slot*: uint64 ##\
## Slot number
block_root*: Eth2Digest ##\
## Block root
signature*: ValidatorSig ##\
## Signature
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#beaconstate
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#beaconstate
BeaconState* = object
slot*: Slot
genesis_time*: uint64
@ -423,9 +412,6 @@ type
## `latest_block_header.state_root == ZERO_HASH` temporarily
historical_roots*: seq[Eth2Digest]
# TOOD remove, gone in 0.5
latest_attestations*: seq[PendingAttestation]
# Ethereum 1.0 chain data
latest_eth1_data*: Eth1Data
eth1_data_votes*: seq[Eth1DataVote]

View File

@ -132,7 +132,7 @@ func get_active_validator_indices*(validators: openArray[Validator], epoch: Epoc
if is_active_validator(val, epoch):
result.add idx.ValidatorIndex
# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#get_epoch_committee_count
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#get_epoch_committee_count
func get_epoch_committee_count*(active_validator_count: int): uint64 =
clamp(
active_validator_count div SLOTS_PER_EPOCH div TARGET_COMMITTEE_SIZE,

View File

@ -39,25 +39,6 @@ func flatten[T](v: openArray[seq[T]]): seq[T] =
# TODO not in nim - doh.
for x in v: result.add x
# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#block-signature
func verifyBlockSignature(state: BeaconState, blck: BeaconBlock): bool =
## When creating a block, the proposer will sign a version of the block that
## doesn't contain the data (chicken and egg), then add the signature to that
## block. Here, we check that the signature is correct by repeating the same
## process.
let
proposer =
state.validator_registry[get_beacon_proposer_index(state, state.slot)]
proposal = Proposal(
slot: blck.slot.uint64,
block_root: Eth2Digest(data: signed_root(blck)),
signature: blck.signature)
bls_verify(
proposer.pubkey,
signed_root(proposal),
proposal.signature,
get_domain(state.fork, get_current_epoch(state), DOMAIN_BEACON_BLOCK))
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#block-header
proc processBlockHeader(
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
@ -70,13 +51,16 @@ proc processBlockHeader(
## https://github.com/ethereum/eth2.0-specs/pull/816/files remove when
## switched to 0.5.1
if not (blck.previous_block_root.data ==
signed_root(state.latest_block_header)):
notice "Block header: previous block root mismatch",
previous_block_root = blck.previous_block_root,
latest_block_header = state.latest_block_header,
latest_block_header_root = hash_tree_root_final(state.latest_block_header)
return false
when false:
## TODO Re-enable when it works; currently, some code in processBlock
## also checks this invariant in a different way. tag as 0.4.0.
if not (blck.previous_block_root.data ==
signed_root(state.latest_block_header)):
notice "Block header: previous block root mismatch",
previous_block_root = blck.previous_block_root,
latest_block_header = state.latest_block_header,
latest_block_header_root = hash_tree_root_final(state.latest_block_header)
return false
state.latest_block_header = get_temporary_block_header(blck)
@ -277,7 +261,7 @@ proc processAttesterSlashings(state: var BeaconState, blck: BeaconBlock): bool =
true
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#attestations
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#attestations
proc processAttestations(
state: var BeaconState, blck: BeaconBlock, flags: UpdateFlags): bool =
## Each block includes a number of attestations that the proposer chose. Each
@ -294,18 +278,23 @@ proc processAttestations(
return false
# All checks passed - update state
state.latest_attestations.add blck.body.attestations.mapIt(
PendingAttestation(
data: it.data,
aggregation_bitfield: it.aggregation_bitfield,
custody_bitfield: it.custody_bitfield,
inclusion_slot: state.slot,
# Apply the attestations
for attestation in blck.body.attestations:
let pending_attestation = PendingAttestation(
data: attestation.data,
aggregation_bitfield: attestation.aggregation_bitfield,
custody_bitfield: attestation.custody_bitfield,
inclusion_slot: state.slot
)
)
if slot_to_epoch(attestation.data.slot) == get_current_epoch(state):
state.current_epoch_attestations.add(pending_attestation)
else:
state.previous_epoch_attestations.add(pending_attestation)
true
# https://github.com/ethereum/eth2.0-specs/blob/v0.3.0/specs/core/0_beacon-chain.md#deposits-1
# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#deposits-1
func processDeposits(state: var BeaconState, blck: BeaconBlock): bool =
true
@ -484,18 +473,11 @@ proc processBlock(
# TODO when there's a failure, we should reset the state!
# TODO probably better to do all verification first, then apply state changes
# https://github.com/ethereum/eth2.0-specs/blob/0.4.0/specs/core/0_beacon-chain.md#slot-1
if not (blck.slot == state.slot):
notice "Unexpected block slot number",
blockSlot = humaneSlotNum(blck.slot),
stateSlot = humaneSlotNum(state.slot)
return false
# Spec does not have this check explicitly, but requires that this condition
# holds - so we give verify it as well - this would happen naturally if
# `blck.previous_block_root` was used in `processSlot` - but that doesn't cut it for
# blockless slot processing.
# TODO compare with check in processBlockHeader, might be redundant
# TODO compare with processBlockHeader check, might be redundant and to be removed
let stateParentRoot =
state.latest_block_roots[(state.slot - 1) mod SLOTS_PER_HISTORICAL_ROOT]
if not (blck.previous_block_root == stateParentRoot):
@ -504,18 +486,9 @@ proc processBlock(
stateParentRoot
return false
# TODO Technically, we could make processBlock take a generic type instead
# of BeaconBlock - we would then have an intermediate `ProposedBlock`
# type that omits some fields - this way, the compiler would guarantee
# that we don't try to access fields that don't have a value yet
#if not processBlockHeader(state, blck, flags):
# notice "Block header not valid", slot = humaneSlotNum(state.slot)
# return false
# TODO this starts requiring refactoring blockpool, etc
if skipValidation notin flags:
if not verifyBlockSignature(state, blck):
notice "Block signature not valid", slot = humaneSlotNum(state.slot)
return false
if not processBlockHeader(state, blck, flags):
notice "Block header not valid", slot = humaneSlotNum(state.slot)
return false
if not processRandao(state, blck, flags):
return false
@ -542,7 +515,7 @@ proc processBlock(
true
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#helper-functions-1
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#helper-functions-1
func get_current_total_balance(state: BeaconState): Gwei =
return get_total_balance(
state,
@ -615,7 +588,9 @@ func get_winning_root_and_participants(state: BeaconState, shard: Shard):
return (ZERO_HASH, @[])
func get_attestations_for(root: Eth2Digest): seq[PendingAttestation] =
filterIt(valid_attestations, it.data.crosslink_data_root == root)
filterIt(
valid_attestations,
it.data.crosslink_data_root == root)
## Winning crosslink root is the root with the most votes for it, ties broken
## in favor of lexicographically higher hash
@ -641,7 +616,7 @@ func inclusion_slots(state: BeaconState): auto =
result = initTable[ValidatorIndex, Slot]()
let previous_epoch_attestations =
state.latest_attestations.filterIt(
state.previous_epoch_attestations.filterIt(
get_previous_epoch(state) == slot_to_epoch(it.data.slot))
## TODO switch previous_epoch_attestations to state.foo,
@ -659,7 +634,7 @@ func inclusion_distances(state: BeaconState): auto =
result = initTable[ValidatorIndex, Slot]()
let previous_epoch_attestations =
state.latest_attestations.filterIt(
state.previous_epoch_attestations.filterIt(
get_previous_epoch(state) == slot_to_epoch(it.data.slot))
## TODO switch previous_epoch_attestations to state.foo,
@ -741,13 +716,20 @@ func update_justification_and_finalization(state: var BeaconState) =
state.finalized_root =
get_block_root(state, get_epoch_start_slot(new_finalized_epoch))
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.0/specs/core/0_beacon-chain.md#crosslinks
# https://github.com/ethereum/eth2.0-specs/blob/v0.5.1/specs/core/0_beacon-chain.md#crosslinks
func process_crosslinks(state: var BeaconState) =
let
current_epoch = get_current_epoch(state)
previous_epoch = current_epoch - 1
next_epoch = current_epoch + 1
for slot in get_epoch_start_slot(previous_epoch).uint64 ..<
## TODO is it actually correct to be setting state.latest_crosslinks[shard]
## to something pre-GENESIS_EPOCH, ever? I guess the intent is if there are
## a quorum of participants for get_epoch_start_slot(previous_epoch), when
## state.slot == GENESIS_SLOT, then there will be participants for a quorum
## in the current-epoch (i.e. genesis epoch) version of that shard?
#for slot in get_epoch_start_slot(previous_epoch).uint64 ..<
for slot in max(
GENESIS_SLOT.uint64, get_epoch_start_slot(previous_epoch).uint64) ..<
get_epoch_start_slot(next_epoch).uint64:
for cas in get_crosslink_committees_at_slot(state, slot):
let
@ -756,7 +738,11 @@ func process_crosslinks(state: var BeaconState) =
get_winning_root_and_participants(state, shard)
participating_balance = get_total_balance(state, participants)
total_balance = get_total_balance(state, crosslink_committee)
if 3'u64 * participating_balance >= 2'u64 * total_balance:
# Check not from spec; seems kludgy
doAssert slot >= GENESIS_SLOT
state.latest_crosslinks[shard] = Crosslink(
epoch: slot_to_epoch(slot),
crosslink_data_root: winning_root
@ -1117,14 +1103,20 @@ proc advanceState*(
## We now define the state transition function. At a high level the state
## transition is made up of four parts:
# 1. State caching, which happens at the start of every slot.
## 1. State caching, which happens at the start of every slot.
## The state caching, caches the state root of the previous slot
cacheState(state)
## (2) The per-epoch transitions, which happens at the start of the first
## 2. The per-epoch transitions, which happens at the start of the first
## slot of every epoch.
## The per-epoch transitions focus on the validator registry, including
## adjusting balances and activating and exiting validators, as well as
## processing crosslinks and managing block justification/finalization.
processEpoch(state)
# (3) The per-slot transitions, which happens at every slot.
## 3. The per-slot transitions, which happens at every slot.
## The per-slot transitions focus on the slot counter and block roots
## records updates.
processSlot(state, previous_block_root)
proc updateState*(

View File

@ -25,13 +25,11 @@ proc getValidator*(pool: ValidatorPool,
pool.validators.getOrDefault(validatorKey)
proc signBlockProposal*(v: AttachedValidator, fork: Fork,
proposal: Proposal): Future[ValidatorSig] {.async.} =
blck: BeaconBlock): Future[ValidatorSig] {.async.} =
if v.kind == inProcess:
await sleepAsync(1)
let proposalRoot = hash_tree_root_final(proposal)
result = bls_sign(v.privKey, signed_root(proposal),
get_domain(fork, slot_to_epoch(proposal.slot), DOMAIN_BEACON_BLOCK))
result = bls_sign(v.privKey, signed_root(blck),
get_domain(fork, slot_to_epoch(blck.slot), DOMAIN_BEACON_BLOCK))
else:
# TODO:
# send RPC

View File

@ -10,16 +10,20 @@ proc stateSize(deposits: int, maxContent = false) =
deposits, {skipValidation}), 0, Eth1Data(), {skipValidation})
if maxContent:
# TODO verify this is correct, but generally we collect up to two epochs
# of attestations, and each block has a cap on the number of
# attestations it may hold, so we'll just add so many of them
state.latest_attestations.setLen(MAX_ATTESTATIONS * SLOTS_PER_EPOCH * 2)
let
crosslink_committees = get_crosslink_committees_at_slot(state, 0.Slot)
validatorsPerCommittee =
len(crosslink_committees[0].committee) # close enough..
for a in state.latest_attestations.mitems():
a.aggregation_bitfield = BitField.init(validatorsPerCommittee)
# TODO: state.latest_attestations was removed
# in https://github.com/status-im/nim-beacon-chain/pull/195
raise newException(ValueError, "Not supported at the moment")
# # TODO verify this is correct, but generally we collect up to two epochs
# # of attestations, and each block has a cap on the number of
# # attestations it may hold, so we'll just add so many of them
# state.latest_attestations.setLen(MAX_ATTESTATIONS * SLOTS_PER_EPOCH * 2)
# let
# crosslink_committees = get_crosslink_committees_at_slot(state, 0.Slot)
# validatorsPerCommittee =
# len(crosslink_committees[0].committee) # close enough..
# for a in state.latest_attestations.mitems():
# a.aggregation_bitfield = BitField.init(validatorsPerCommittee)
echo "Validators: ", deposits, ", total: ", SSZ.encode(state).len
dispatch(stateSize)

View File

@ -101,7 +101,7 @@ suite "Block processing":
discard updateState(state, previous_block_root, new_block, {})
check:
state.latest_attestations.len == 1
state.current_epoch_attestations.len == 1
while state.slot < 191:
advanceState(state, previous_block_root)

View File

@ -118,31 +118,21 @@ proc addBlock*(
# can set the state root in order to be able to create a valid signature
new_block.state_root = Eth2Digest(data: hash_tree_root(state))
let
proposerPrivkey = hackPrivKey(proposer)
# Once we've collected all the state data, we sign the block data along with
# some book-keeping values
signed_data = Proposal(
slot: new_block.slot.uint64,
block_root: Eth2Digest(data: signed_root(new_block)),
signature: ValidatorSig(),
)
proposal_hash = signed_root(signed_data)
let proposerPrivkey = hackPrivKey(proposer)
doAssert proposerPrivkey.pubKey() == proposer.pubkey,
"signature key should be derived from private key! - wrong privkey?"
if skipValidation notin flags:
let block_root = signed_root(new_block)
# We have a signature - put it in the block and we should be done!
new_block.signature =
bls_sign(proposerPrivkey, proposal_hash,
get_domain(state.fork, slot_to_epoch(state.slot), DOMAIN_BEACON_BLOCK))
bls_sign(proposerPrivkey, block_root,
get_domain(state.fork, slot_to_epoch(new_block.slot), DOMAIN_BEACON_BLOCK))
doAssert bls_verify(
proposer.pubkey,
proposal_hash, new_block.signature,
get_domain(state.fork, slot_to_epoch(state.slot), DOMAIN_BEACON_BLOCK)),
block_root, new_block.signature,
get_domain(state.fork, slot_to_epoch(new_block.slot), DOMAIN_BEACON_BLOCK)),
"we just signed this message - it should pass verification!"
new_block