Skip to content

Commit

Permalink
spec updates
Browse files Browse the repository at this point in the history
* first attestation created!
* add hash_tree_root_final that returns an Eth2Digest
* hits the first real blocking spec bug :(
  • Loading branch information
arnetheduck authored and mratsim committed Dec 24, 2018
1 parent 0431458 commit eb369ce
Show file tree
Hide file tree
Showing 13 changed files with 141 additions and 36 deletions.
4 changes: 2 additions & 2 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ proc proposeBlock(node: BeaconNode,
var signedData: ProposalSignedData
signedData.slot = node.beaconState.slot
signedData.shard = BEACON_CHAIN_SHARD_NUMBER
signedData.blockRoot.data = hash_tree_root(proposal)
signedData.blockRoot = hash_tree_root_final(proposal)

proposal.signature = await validator.signBlockProposal(signedData)
await node.network.broadcast(topicBeaconBlocks, proposal)
Expand Down Expand Up @@ -235,7 +235,7 @@ when isMainModule:
config.chainStartupData.validatorDeposits,
config.chainStartupData.genesisTime,
Eth2Digest())

Json.saveFile(outfile, initialState, pretty = true)
echo "Wrote ", outfile
quit 0
Expand Down
15 changes: 7 additions & 8 deletions beacon_chain/spec/beaconstate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ func process_deposit(state: var BeaconState,
withdrawal_credentials: Eth2Digest,
randao_commitment: Eth2Digest): Uint24 =
## Process a deposit from Ethereum 1.0.
let msg = hash_tree_root((pubkey, withdrawal_credentials, randao_commitment))
let msg = hash_tree_root_final(
(pubkey, withdrawal_credentials, randao_commitment))
assert bls_verify(
pubkey, msg, proof_of_possession,
pubkey, msg.data, proof_of_possession,
get_domain(state.fork_data, state.slot, DOMAIN_DEPOSIT))

let validator_pubkeys = mapIt(state.validator_registry, it.pubkey)
Expand Down Expand Up @@ -249,10 +250,8 @@ func get_attestation_participants*(state: BeaconState,
# TODO investigate functional library / approach to help avoid loop bugs
assert len(participation_bitfield) == ceil_div8(len(snc.committee))
for i, vindex in snc.committee:
let
bit = (participation_bitfield[i div 8] shr (7 - (i mod 8))) mod 2
if bit == 1:
result.add(vindex)
if bitIsSet(participation_bitfield, i):
result.add(vindex)
return # found the shard, we're done

func process_ejections*(state: var BeaconState) =
Expand Down Expand Up @@ -326,10 +325,10 @@ proc checkAttestation*(state: BeaconState, attestation: Attestation): bool =
participants, state.validator_registry[it].pubkey))

# Verify that aggregate_signature verifies using the group pubkey.
let msg = hash_tree_root(attestation.data)
let msg = hash_tree_root_final(attestation.data)

if not bls_verify(
group_public_key, @msg & @[0'u8], attestation.aggregate_signature,
group_public_key, @(msg.data) & @[0'u8], attestation.aggregate_signature,
get_domain(state.fork_data, attestation.data.slot, DOMAIN_ATTESTATION)
):
warn("Invalid attestation group signature")
Expand Down
2 changes: 1 addition & 1 deletion beacon_chain/spec/crypto.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ template hash*(k: ValidatorPubKey|ValidatorPrivKey): Hash =
func pubKey*(pk: ValidatorPrivKey): ValidatorPubKey = fromSigKey(pk)

func bls_aggregate_pubkeys*(keys: openArray[ValidatorPubKey]): ValidatorPubKey =
var empty = false
var empty = true
for key in keys:
if empty:
result = key
Expand Down
2 changes: 2 additions & 0 deletions beacon_chain/spec/datatypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import
# Eventually, we could also differentiate between user/tainted data and
# internal state that's gone through sanity checks already.

# TODO Many of these constants should go into a config object that can be used
# to run.. well.. a chain with different constants!
const
SHARD_COUNT* = 1024 ##\
## Number of shards supported by the network - validators will jump around
Expand Down
8 changes: 8 additions & 0 deletions beacon_chain/spec/helpers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@

import ./datatypes, ./digest, sequtils, math

# TODO spec candidate? there's bits in nim-ranges but that one has some API
# issues regarding bit endianess that need resolving..
func bitIsSet*(bitfield: openArray[byte], index: int): bool =
(bitfield[index div 8] shr byte(7 - (index mod 8))) mod 2 > 0'u8

func bitSet*(bitfield: var openArray[byte], index: int) =
bitfield[index div 8] = bitfield[index div 8] or 1'u8 shl (7 - (index mod 8))

func mod_get[T](arr: openarray[T], pos: Natural): T =
arr[pos mod arr.len]

Expand Down
4 changes: 2 additions & 2 deletions beacon_chain/spec/validator.nim
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,12 @@ func get_new_validator_registry_delta_chain_tip*(
flag: ValidatorSetDeltaFlags): Eth2Digest =
## Compute the next hash in the validator registry delta hash chain.

Eth2Digest(data: hash_tree_root(ValidatorRegistryDeltaBlock(
hash_tree_root_final(ValidatorRegistryDeltaBlock(
latest_registry_delta_root: current_validator_registry_delta_chain_tip,
validator_index: index,
pubkey: pubkey,
flag: flag
)))
))

func get_effective_balance*(validator: ValidatorRecord): uint64 =
min(validator.balance, MAX_DEPOSIT * GWEI_PER_ETH)
Expand Down
7 changes: 7 additions & 0 deletions beacon_chain/ssz.nim
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ func hash_tree_root*(x: ValidatorSig): array[32, byte] =
## This is a "stub" needed for BeaconBlock hashing
x.getRaw().hash()

func hash_tree_root_final*(x: object|tuple): Eth2Digest =
# TODO suggested for spec:
# https://github.com/ethereum/eth2.0-specs/issues/276
# only for objects now, else the padding would have to be implemented - not
# needed yet..
Eth2Digest(data: hash_tree_root(x))

# ################### Tree hash ###################################

func merkleHash[T](lst: openArray[T]): array[32, byte] =
Expand Down
13 changes: 7 additions & 6 deletions beacon_chain/state_transition.nim
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ func verifyProposerSignature(state: BeaconState, blck: BeaconBlock): bool =
signed_data = ProposalSignedData(
slot: state.slot,
shard: BEACON_CHAIN_SHARD_NUMBER,
block_root: Eth2Digest(data: hash_tree_root(blck_without_sig))
block_root: hash_tree_root_final(blck_without_sig)
)
proposal_hash = hash_tree_root(signed_data)
proposal_hash = hash_tree_root_final(signed_data)
proposer_index = get_beacon_proposer_index(state, state.slot)

bls_verify(
state.validator_registry[proposer_index].pubkey,
proposal_hash, blck.signature,
proposal_hash.data, blck.signature,
get_domain(state.fork_data, state.slot, DOMAIN_PROPOSAL))

func processRandao(
Expand Down Expand Up @@ -132,7 +132,7 @@ proc processProposerSlashings(state: var BeaconState, blck: BeaconBlock): bool =
let proposer = addr state.validator_registry[proposer_slashing.proposer_index]
if not bls_verify(
proposer.pubkey,
hash_tree_root(proposer_slashing.proposal_data_1),
hash_tree_root_final(proposer_slashing.proposal_data_1).data,
proposer_slashing.proposal_signature_1,
get_domain(
state.fork_data, proposer_slashing.proposal_data_1.slot,
Expand All @@ -141,7 +141,7 @@ proc processProposerSlashings(state: var BeaconState, blck: BeaconBlock): bool =
return false
if not bls_verify(
proposer.pubkey,
hash_tree_root(proposer_slashing.proposal_data_2),
hash_tree_root_final(proposer_slashing.proposal_data_2).data,
proposer_slashing.proposal_signature_2,
get_domain(
state.fork_data, proposer_slashing.proposal_data_2.slot,
Expand Down Expand Up @@ -469,6 +469,7 @@ func processEpoch(state: var BeaconState) =
state.slot <= it.data.slot + 2 * EPOCH_LENGTH and
it.data.slot + EPOCH_LENGTH < state.slot)

let
previous_epoch_attesters =
get_attesters(state, previous_epoch_attestations)

Expand Down Expand Up @@ -755,7 +756,7 @@ func processEpoch(state: var BeaconState) =
)

proc verifyStateRoot(state: BeaconState, blck: BeaconBlock): bool =
let state_root = Eth2Digest(data: hash_tree_root(state))
let state_root = hash_tree_root_final(state)
if state_root != blck.state_root:
warn("Block: root verification failed",
block_state_root = blck.state_root, state_root)
Expand Down
4 changes: 2 additions & 2 deletions beacon_chain/validator_keygen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ proc main() =
genSingleValidator(outPath / &"validator-{i:02}.json")

let withdrawalCredentials = makeFakeHash(i)
let proofOfPossession = signMessage(privkey, hash_tree_root(
(pubKey, withdrawalCredentials, randaoCommitment)))
let proofOfPossession = signMessage(privkey, hash_tree_root_final(
(pubKey, withdrawalCredentials, randaoCommitment)).data)

startupData.validatorDeposits.add Deposit(
deposit_data: DepositData(
Expand Down
8 changes: 4 additions & 4 deletions beacon_chain/validator_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,10 @@ proc signBlockProposal*(v: AttachedValidator,
proposal: ProposalSignedData): Future[ValidatorSig] {.async.} =
if v.kind == inProcess:
await sleepAsync(1)
let proposalRoot = hash_tree_root(proposal)
let proposalRoot = hash_tree_root_final(proposal)

# TODO: Should we use proposalRoot as data, or digest in regards to signature?
return signMessage(v.privKey, proposalRoot)
return signMessage(v.privKey, proposalRoot.data)
else:
# TODO:
# send RPC
Expand All @@ -62,9 +62,9 @@ proc signAttestation*(v: AttachedValidator,
if v.kind == inProcess:
await sleepAsync(1)

let attestationRoot = hash_tree_root(attestation)
let attestationRoot = hash_tree_root_final(attestation)
# TODO: Should we use attestationRoot as data, or digest in regards to signature?
return signMessage(v.privKey, attestationRoot)
return signMessage(v.privKey, attestationRoot.data)
else:
# TODO:
# send RPC
Expand Down
6 changes: 3 additions & 3 deletions research/state_sim.nim
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ proc transition(

var
state = genesisState
latest_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
latest_block_root = hash_tree_root_final(genesisBlock)

for i in 0..<slots:
if state.slot mod json_interval.uint64 == 0:
Expand All @@ -36,8 +36,8 @@ proc transition(
else:
write(stdout, ".")

latest_block_root = Eth2Digest(data: hash_tree_root(
addBlock(state, latest_block_root, BeaconBlockBody())))
latest_block_root = hash_tree_root_final(
addBlock(state, latest_block_root, BeaconBlockBody()))

flushFile(stdout)

Expand Down
52 changes: 47 additions & 5 deletions tests/test_state_transition.nim
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ suite "Block processing":
let
state = genesisState
proposer_index = getNextBeaconProposerIndex(state)
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
previous_block_root = hash_tree_root_final(genesisBlock)
new_state = updateState(
state, previous_block_root, none(BeaconBlock), {})
check:
Expand All @@ -42,7 +42,7 @@ suite "Block processing":
let
state = genesisState
proposer_index = getNextBeaconProposerIndex(state)
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
previous_block_root = hash_tree_root_final(genesisBlock)
new_block = makeBlock(state, previous_block_root, BeaconBlockBody())
new_state = updateState(
state, previous_block_root, some(new_block), {})
Expand All @@ -59,7 +59,7 @@ suite "Block processing":
test "Passes through epoch update, no block":
var
state = genesisState
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
previous_block_root = hash_tree_root_final(genesisBlock)

for i in 1..EPOCH_LENGTH.int:
let new_state = updateState(
Expand All @@ -74,7 +74,7 @@ suite "Block processing":
test "Passes through epoch update, empty block":
var
state = genesisState
previous_block_root = Eth2Digest(data: hash_tree_root(genesisBlock))
previous_block_root = hash_tree_root_final(genesisBlock)

for i in 1..EPOCH_LENGTH.int:
var new_block = makeBlock(state, previous_block_root, BeaconBlockBody())
Expand All @@ -86,7 +86,49 @@ suite "Block processing":
new_state.block_ok
state = new_state.state
if new_state.block_ok:
previous_block_root = Eth2Digest(data: hash_tree_root(new_block))
previous_block_root = hash_tree_root_final(new_block)

check:
state.slot == genesisState.slot + EPOCH_LENGTH

test "Attestation gets processed at epoch":
var
state = genesisState
previous_block_root = hash_tree_root_final(genesisBlock)

# Slot 0 is a finalized slot - won't be making attestations for it..
state = updateState(
state, previous_block_root, none(BeaconBlock), {}).state

let
# Create an attestation for slot 1 signed by the only attester we have!
attestation = makeAttestation(
state, previous_block_root,
state.shard_committees_at_slots[state.slot][0].committee[0])

# Some time needs to pass before attestations are included - this is
# to let the attestation propagate properly to interested participants
while state.slot < MIN_ATTESTATION_INCLUSION_DELAY + 1:
state = updateState(
state, previous_block_root, none(BeaconBlock), {}).state

let
new_block = makeBlock(state, previous_block_root, BeaconBlockBody(
attestations: @[attestation]
))
state = updateState(state, previous_block_root, some(new_block), {}).state

check:
state.latest_attestations.len == 1

# TODO Can't run more than 127 for now:
# https://github.com/ethereum/eth2.0-specs/issues/352
while state.slot < 127:
state = updateState(
state, previous_block_root, none(BeaconBlock), {}).state

# Would need to process more epochs for the attestation to be removed from
# the state! (per above bug)
#
# check:
# state.latest_attestations.len == 0
52 changes: 49 additions & 3 deletions tests/testutil.nim
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.

import
options, milagro_crypto,
options, milagro_crypto, sequtils,
../beacon_chain/[extras, ssz, state_transition],
../beacon_chain/spec/[crypto, datatypes, digest, helpers]
../beacon_chain/spec/[beaconstate, crypto, datatypes, digest, helpers]

const
randaoRounds = 100
Expand Down Expand Up @@ -107,7 +107,7 @@ proc addBlock*(
randao_reveal: hackReveal(proposer),
candidate_pow_receipt_root: Eth2Digest(), # TODO
signature: ValidatorSig(), # we need the rest of the block first!
body: BeaconBlockBody() # TODO throw in stuff here...
body: body
)

var block_ok: bool
Expand Down Expand Up @@ -156,3 +156,49 @@ proc makeBlock*(
# because the block includes the state root.
var next_state = state
addBlock(next_state, previous_block_root, body)

proc find_shard_committee(
sacs: openArray[ShardCommittee], validator_index: Uint24): ShardCommittee =
for sac in sacs:
if validator_index in sac.committee: return sac
doAssert false

proc makeAttestation*(
state: BeaconState, beacon_block_root: Eth2Digest,
validator_index: Uint24): Attestation =

let new_state = updateState(
state, beacon_block_root, none(BeaconBlock), {skipValidation})
let
sac = find_shard_committee(
get_shard_committees_at_slot(state, state.slot), validator_index)
validator = state.validator_registry[validator_index]
sac_index = sac.committee.find(validator_index)

data = AttestationData(
slot: state.slot,
shard: sac.shard,
beacon_block_root: beacon_block_root,
epoch_boundary_root: Eth2Digest(), # TODO
shard_block_root: Eth2Digest(), # TODO
latest_crosslink_root: Eth2Digest(), # TODO
justified_slot: state.justified_slot,
justified_block_root:
get_block_root(new_state.state, state.justified_slot),
)

assert sac_index != -1, "find_shard_committe should guarantee this"

var
participation_bitfield = repeat(0'u8, ceil_div8(sac.committee.len))
bitSet(participation_bitfield, sac_index)

let
msg = hash_tree_root_final(data)

Attestation(
data: data,
participation_bitfield: participation_bitfield,
aggregate_signature: signMessage(
hackPrivKey(validator), @(msg.data) & @[0'u8])
)

0 comments on commit eb369ce

Please sign in to comment.