update to 0.9.1: don't try to run removed tests; re-add 1.5 tests (one only in minimal); remove bls_verify_multiple(...) and AttestationDataAndCustodyBit; and update process_attester_slashing(...), get_indexed_attestation(...), and is_valid_indexed_attestation(...)

This commit is contained in:
Dustin Brody 2019-11-13 12:30:11 +01:00
parent 9af418c30d
commit 5a54c823d8
13 changed files with 68 additions and 177 deletions

View File

@ -139,6 +139,13 @@ proc updateLatestVotes(
if current.isNil or current.slot < attestationSlot:
pool.latestAttestations[pubKey] = blck
func get_attesting_indices_seq(state: BeaconState,
attestation_data: AttestationData,
bits: CommitteeValidatorsBits): seq[ValidatorIndex] =
var cache = get_empty_per_epoch_cache()
toSeq(items(get_attesting_indices(
state, attestation_data, bits, cache)))
proc add*(pool: var AttestationPool,
state: BeaconState,
blck: BlockRef,
@ -169,7 +176,6 @@ proc add*(pool: var AttestationPool,
slotData = addr pool.slots[idx]
validation = Validation(
aggregation_bits: attestation.aggregation_bits,
custody_bits: attestation.custody_bits,
aggregate_signature: attestation.signature)
participants = get_attesting_indices_seq(
state, attestation.data, validation.aggregation_bits)
@ -289,7 +295,6 @@ proc getAttestationsForBlock*(
attestation = Attestation(
aggregation_bits: a.validations[0].aggregation_bits,
data: a.data,
custody_bits: a.validations[0].custody_bits,
signature: a.validations[0].aggregate_signature
)
@ -321,7 +326,6 @@ proc getAttestationsForBlock*(
# one new attestation in there
if not attestation.aggregation_bits.overlaps(v.aggregation_bits):
attestation.aggregation_bits.combine(v.aggregation_bits)
attestation.custody_bits.combine(v.custody_bits)
attestation.signature.combine(v.aggregate_signature)
result.add(attestation)

View File

@ -310,9 +310,7 @@ proc sendAttestation(node: BeaconNode,
var attestation = Attestation(
data: attestationData,
signature: validatorSignature,
aggregation_bits: aggregationBits,
# Stub in phase0
custody_bits: CommitteeValidatorsBits.init(committeeLen)
aggregation_bits: aggregationBits
)
node.network.broadcast(topicAttestations, attestation)

View File

@ -47,8 +47,6 @@ type
# #############################################
Validation* = object
aggregation_bits*: CommitteeValidatorsBits
custody_bits*: CommitteeValidatorsBits ##\
## Phase 1 - the handling of this field is probably broken..
aggregate_signature*: ValidatorSig
# Per Danny as of 2018-12-21:

View File

@ -321,63 +321,33 @@ func process_registry_updates*(state: var BeaconState) =
validator.activation_epoch =
compute_activation_exit_epoch(get_current_epoch(state))
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.0/specs/core/0_beacon-chain.md#is_valid_indexed_attestation
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#is_valid_indexed_attestation
proc is_valid_indexed_attestation*(
state: BeaconState, indexed_attestation: IndexedAttestation): bool =
## Check if ``indexed_attestation`` has valid indices and signature.
# TODO: this is noSideEffect besides logging
# https://github.com/status-im/nim-chronicles/issues/62
let
bit_0_indices = indexed_attestation.custody_bit_0_indices.asSeq
bit_1_indices = indexed_attestation.custody_bit_1_indices.asSeq
# Verify no index has custody bit equal to 1 [to be removed in phase 1]
if len(bit_1_indices) != 0: # [to be removed in phase 1]
notice "indexed attestation: custody_bit equal to 1"
return false # [to be removed in phase 1]
let indices = indexed_attestation.attesting_indices
# Verify max number of indices
let combined_len = len(bit_0_indices) + len(bit_1_indices)
if not (combined_len <= MAX_VALIDATORS_PER_COMMITTEE):
if not (len(indices) <= MAX_VALIDATORS_PER_COMMITTEE):
notice "indexed attestation: validator index beyond max validators per committee"
return false
# Verify index sets are disjoint
if len(intersection(bit_0_indices.toSet, bit_1_indices.toSet)) != 0:
notice "indexed attestation: indices set not disjoint"
return false
# Verify indices are sorted
if bit_0_indices != sorted(bit_0_indices, system.cmp):
notice "indexed attestation: indices 0 not sorted"
return false
if bit_1_indices != sorted(bit_1_indices, system.cmp):
notice "indexed attestation: indices 0 not sorted"
# TODO but why? this is a local artifact
if indices != sorted(indices, system.cmp):
notice "indexed attestation: indices not sorted"
return false
# Verify aggregate signature
let
pubkeys = @[ # TODO, bls_verify_multiple should accept openarray
bls_aggregate_pubkeys(mapIt(bit_0_indices, state.validators[it.int].pubkey)),
bls_aggregate_pubkeys(mapIt(bit_1_indices, state.validators[it.int].pubkey)),
]
msg1 = AttestationDataAndCustodyBit(
data: indexed_attestation.data, custody_bit: false)
msg2 = AttestationDataAndCustodyBit(
data: indexed_attestation.data, custody_bit: true)
message_hashes = [
hash_tree_root(msg1),
hash_tree_root(msg2),
]
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch)
result = bls_verify_multiple(
pubkeys,
message_hashes,
result = bls_verify(
bls_aggregate_pubkeys(mapIt(indices, state.validators[it.int].pubkey)),
hash_tree_root(indexed_attestation.data).data,
indexed_attestation.signature,
domain,
get_domain(
state, DOMAIN_BEACON_ATTESTER, indexed_attestation.data.target.epoch)
)
if not result:
notice "indexed attestation: signature verification failure"
@ -395,14 +365,6 @@ func get_attesting_indices*(state: BeaconState,
if bits[i]:
result.incl index
# TODO remove after removing attestation pool legacy usage
func get_attesting_indices_seq*(state: BeaconState,
attestation_data: AttestationData,
bits: CommitteeValidatorsBits): seq[ValidatorIndex] =
var cache = get_empty_per_epoch_cache()
toSeq(items(get_attesting_indices(
state, attestation_data, bits, cache)))
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#get_indexed_attestation
func get_indexed_attestation*(state: BeaconState, attestation: Attestation,
stateCache: var StateCache): IndexedAttestation =
@ -411,16 +373,6 @@ func get_indexed_attestation*(state: BeaconState, attestation: Attestation,
attesting_indices =
get_attesting_indices(
state, attestation.data, attestation.aggregation_bits, stateCache)
custody_bit_1_indices =
get_attesting_indices(
state, attestation.data, attestation.custody_bits, stateCache)
doAssert custody_bit_1_indices <= attesting_indices
let
# custody_bit_0_indices = attesting_indices.difference(custody_bit_1_indices)
custody_bit_0_indices =
filterIt(toSeq(items(attesting_indices)), it notin custody_bit_1_indices)
## TODO No fundamental reason to do so many type conversions
## verify_indexed_attestation checks for sortedness but it's
@ -431,18 +383,13 @@ func get_indexed_attestation*(state: BeaconState, attestation: Attestation,
## 0.6.3 highlights and explicates) except in that the spec,
## for no obvious reason, verifies it.
IndexedAttestation(
custody_bit_0_indices: CustodyBitIndices sorted(
mapIt(custody_bit_0_indices, it.uint64), system.cmp),
# toSeq pointlessly constructs int-indexable copy so mapIt can infer type;
# see above
custody_bit_1_indices: CustodyBitIndices sorted(
mapIt(toSeq(items(custody_bit_1_indices)), it.uint64),
system.cmp),
attesting_indices:
sorted(mapIt(attesting_indices.toSeq, it.uint64), system.cmp),
data: attestation.data,
signature: attestation.signature,
signature: attestation.signature
)
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.0/specs/core/0_beacon-chain.md#attestations
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#attestations
proc check_attestation*(
state: BeaconState, attestation: Attestation, flags: UpdateFlags,
stateCache: var StateCache): bool =
@ -477,12 +424,6 @@ proc check_attestation*(
return
let committee = get_beacon_committee(state, data.slot, data.index, stateCache)
if attestation.aggregation_bits.len != attestation.custody_bits.len:
warn("Inconsistent aggregation and custody bits",
aggregation_bits_len = attestation.aggregation_bits.len,
custody_bits_len = attestation.custody_bits.len
)
return
if attestation.aggregation_bits.len != committee.len:
warn("Inconsistent aggregation and committee length",
aggregation_bits_len = attestation.aggregation_bits.len,

View File

@ -178,35 +178,16 @@ func bls_verify*(
if sig.kind != Real or pubkey.kind != Real:
# TODO: chronicles warning
return false
# TODO bls_verify_multiple(...) used to have this workaround, and now it
# lives here. No matter the signature, there's also no meaningful way to
# verify it -- it's a kind of vacuous truth. No pubkey/sig pairs.
if pubkey == ValidatorPubKey():
return true
sig.blsValue.verify(msg, domain, pubkey.blsValue)
else:
sig.verify(msg, domain, pubkey)
# https://github.com/ethereum/eth2.0-specs/blob/v0.8.4/specs/bls_signature.md#bls_verify_multiple
proc bls_verify_multiple*(
pubkeys: seq[ValidatorPubKey], message_hashes: openArray[Eth2Digest],
sig: ValidatorSig, domain: Domain): bool =
# {.noSideEffect.} - https://github.com/status-im/nim-chronicles/issues/62
let L = len(pubkeys)
doAssert L == len(message_hashes)
if sig.kind != Real:
warn "Raw bytes do not match with a BLS signature."
return false
# TODO optimize using multiPairing
for pubkey_message_hash in zip(pubkeys, message_hashes):
let (pubkey, message_hash) = pubkey_message_hash
doAssert pubkey.kind == Real
# TODO spec doesn't say to handle this specially, but it's silly to
# validate without any actual public keys.
if pubkey.blsValue == VerKey():
trace "Received empty public key, skipping verification."
continue
if not sig.blsValue.verify(message_hash.data, domain, pubkey.blsValue):
return false
true
when ValidatorPrivKey is BlsValue:
func bls_sign*(key: ValidatorPrivKey, msg: openarray[byte],
domain: Domain): ValidatorSig =

View File

@ -40,7 +40,6 @@ import
# Constant presets
# https://github.com/ethereum/eth2.0-specs/tree/v0.6.3/configs/constant_presets/
const const_preset* {.strdefine.} = "minimal"
when const_preset == "mainnet":
@ -53,7 +52,7 @@ else:
{.fatal: "Preset \"" & const_preset ".nim\" is not supported.".}
const
SPEC_VERSION* = "0.9.0" ## \
SPEC_VERSION* = "0.9.1" ## \
## Spec version we're aiming to be compatible with, right now
## TODO: improve this scheme once we can negotiate versions in protocol
@ -81,9 +80,11 @@ type
# to the system wordsize. This lifts smaller, and now incorrect,
# range-limit.
ValidatorIndex* = distinct uint32
Shard* = uint64
Gwei* = uint64
# TODO remove
Shard* = uint64
BitList*[maxLen: static int] = distinct BitSeq
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#proposerslashing
@ -97,26 +98,18 @@ type
attestation_1*: IndexedAttestation
attestation_2*: IndexedAttestation
CustodyBitIndices* = List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.0/specs/core/0_beacon-chain.md#indexedattestation
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#indexedattestation
IndexedAttestation* = object
custody_bit_0_indices*: CustodyBitIndices ##\
## Indices with custody bit equal to 0
custody_bit_1_indices*: CustodyBitIndices ##\
## Indices with custody bit equal to 1
attesting_indices*: List[uint64, MAX_VALIDATORS_PER_COMMITTEE]
data*: AttestationData
signature*: ValidatorSig
CommitteeValidatorsBits* = BitList[MAX_VALIDATORS_PER_COMMITTEE]
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.0/specs/core/0_beacon-chain.md#attestation
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#attestation
Attestation* = object
aggregation_bits*: CommitteeValidatorsBits
data*: AttestationData
custody_bits*: CommitteeValidatorsBits
signature*: ValidatorSig
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#checkpoint
@ -136,13 +129,6 @@ type
source*: Checkpoint
target*: Checkpoint
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.0/specs/core/0_beacon-chain.md#attestationdataandcustodybit
AttestationDataAndCustodyBit* = object
data*: AttestationData
custody_bit*: bool ##\
## Challengeable bit (SSZ-bool, 1 byte) for the custody of shard data
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#deposit
Deposit* = object
proof*: array[DEPOSIT_CONTRACT_TREE_DEPTH + 1, Eth2Digest] ##\
@ -201,11 +187,11 @@ type
graffiti*: Eth2Digest # TODO make that raw bytes
# Operations
proposer_slashings*: seq[ProposerSlashing]
attester_slashings*: seq[AttesterSlashing]
attestations*: seq[Attestation]
deposits*: seq[Deposit]
voluntary_exits*: seq[VoluntaryExit]
proposer_slashings*: List[ProposerSlashing, MAX_PROPOSER_SLASHINGS]
attester_slashings*: List[AttesterSlashing, MAX_ATTESTER_SLASHINGS]
attestations*: List[Attestation, MAX_ATTESTATIONS]
deposits*: List[Deposit, MAX_DEPOSITS]
voluntary_exits*: List[VoluntaryExit, MAX_VOLUNTARY_EXITS]
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#beaconstate
BeaconStateNew* = object
@ -409,7 +395,6 @@ template foreachSpecType*(op: untyped) =
## for populating RTTI tables that concern them.
op Attestation
op AttestationData
op AttestationDataAndCustodyBit
op AttesterSlashing
op BeaconBlock
op BeaconBlockBody
@ -439,8 +424,6 @@ macro fieldMaxLen*(x: typed): untyped =
let size = case $x[1]
of "pubkeys",
"compact_validators",
"custody_bit_0_indices",
"custody_bit_1_indices",
"aggregation_bits",
"custody_bits": int64(MAX_VALIDATORS_PER_COMMITTEE)
of "proposer_slashings": MAX_PROPOSER_SLASHINGS

View File

@ -216,7 +216,7 @@ func is_slashable_attestation_data(
(data_1.source.epoch < data_2.source.epoch and
data_2.target.epoch < data_1.target.epoch)
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.0/specs/core/0_beacon-chain.md#attester-slashings
# https://github.com/ethereum/eth2.0-specs/blob/v0.9.1/specs/core/0_beacon-chain.md#attester-slashings
proc process_attester_slashing*(
state: var BeaconState,
attester_slashing: AttesterSlashing,
@ -244,12 +244,11 @@ proc process_attester_slashing*(
## TODO there's a lot of sorting/set construction here and
## verify_indexed_attestation, but go by spec unless there
## is compelling perf evidence otherwise.
let
attesting_indices_1 = attestation_1.custody_bit_0_indices & attestation_1.custody_bit_1_indices
attesting_indices_2 = attestation_2.custody_bit_0_indices & attestation_2.custody_bit_1_indices
for index in sorted(toSeq(intersection(toSet(attesting_indices_1),
toSet(attesting_indices_2)).items), system.cmp):
if is_slashable_validator(state.validators[index.int], get_current_epoch(state)):
for index in sorted(toSeq(intersection(
toSet(attestation_1.attesting_indices),
toSet(attestation_2.attesting_indices)).items), system.cmp):
if is_slashable_validator(
state.validators[index.int], get_current_epoch(state)):
slash_validator(state, index.ValidatorIndex, stateCache)
slashed_any = true
if not slashed_any:

View File

@ -50,8 +50,7 @@ proc signAttestation*(v: AttachedValidator,
state: BeaconState): Future[ValidatorSig] {.async.} =
if v.kind == inProcess:
let
attestationRoot = hash_tree_root(
AttestationDataAndCustodyBit(data: attestation, custody_bit: false))
attestationRoot = hash_tree_root(attestation)
domain = get_domain(state, DOMAIN_BEACON_ATTESTER, attestation.target.epoch)
# TODO this is an ugly hack to fake a delay and subsequent async reordering

View File

@ -63,10 +63,7 @@ proc get_attestation_signature(
privkey: ValidatorPrivKey
): ValidatorSig =
let msg = AttestationDataAndCustodyBit(
data: attestation_data,
custody_bit: false
).hash_tree_root()
let msg = attestation_data.hash_tree_root()
return bls_sign(
key = privkey,
@ -115,20 +112,19 @@ proc mockAttestationImpl(
committees_per_slot * (slot mod SLOTS_PER_EPOCH)
) mod SHARD_COUNT
crosslink_committee = get_beacon_committee(
beacon_committee = get_beacon_committee(
state,
result.data.slot,
result.data.index,
cache
)
committee_size = crosslink_committee.len
committee_size = beacon_committee.len
result.data = mockAttestationData(state, slot, shard)
result.aggregation_bits = init(CommitteeValidatorsBits, committee_size)
result.custody_bits = init(CommitteeValidatorsBits, committee_size)
# fillAggregateAttestation
for i in 0 ..< crosslink_committee.len:
for i in 0 ..< beacon_committee.len:
result.aggregation_bits[i] = true
if skipValidation notin flags:

View File

@ -16,7 +16,7 @@ import
./fixtures_utils,
../helpers/debug_state
const OperationsAttestationsDir = FixturesDir/"tests-v0.9.0"/const_preset/"phase0"/"operations"/"attestation"/"pyspec_tests"
const OperationsAttestationsDir = SszTestsDir/const_preset/"phase0"/"operations"/"attestation"/"pyspec_tests"
template runTest(testName: string, identifier: untyped) =
# We wrap the tests in a proc to avoid running out of globals
@ -76,10 +76,6 @@ suite "Official - Operations - Attestations " & preset():
runTest("source root is target root", source_root_is_target_root)
runTest("invalid current source root", invalid_current_source_root)
runTest("bad source root", bad_source_root)
runTest("inconsistent bits", inconsistent_bits)
runTest("non-empty custody bits", non_empty_custody_bits)
runTest("empty aggregation bits", empty_aggregation_bits)
runTest("too many aggregation bits", too_many_aggregation_bits)
runTest("too few aggregation bits", too_few_aggregation_bits)
runTest("too many custody bits", too_many_custody_bits)
runTest("too few custody bits", too_few_custody_bits)

View File

@ -16,7 +16,7 @@ import
./fixtures_utils,
../helpers/debug_state
const OpAttSlashingDir = FixturesDir/"tests-v0.9.0"/const_preset/"phase0"/"operations"/"attester_slashing"/"pyspec_tests"
const OpAttSlashingDir = SszTestsDir/const_preset/"phase0"/"operations"/"attester_slashing"/"pyspec_tests"
template runTest(identifier: untyped) =
# We wrap the tests in a proc to avoid running out of globals
@ -75,12 +75,8 @@ suite "Official - Operations - Attester slashing " & preset():
runTest(same_data)
runTest(no_double_or_surround)
runTest(participants_already_slashed)
runTest(custody_bit_0_and_1_intersect)
when false: # TODO - https://github.com/status-im/nim-beacon-chain/issues/429
runTest(att1_bad_extra_index)
runTest(att1_bad_replaced_index)
runTest(att2_bad_extra_index)
runTest(att2_bad_replaced_index)
runTest(unsorted_att_1_bit0)
runTest(unsorted_att_2_bit0)

View File

@ -7,7 +7,7 @@
import
# Standard library
os, unittest, strutils,
os, unittest,
# Beacon chain internals
../../beacon_chain/spec/[datatypes],
../../beacon_chain/[ssz, state_transition, extras],
@ -16,7 +16,7 @@ import
./fixtures_utils,
../helpers/debug_state
const SanityBlocksDir = FixturesDir/"tests-v0.9.0"/const_preset/"phase0"/"sanity"/"blocks"/"pyspec_tests"
const SanityBlocksDir = SszTestsDir/const_preset/"phase0"/"sanity"/"blocks"/"pyspec_tests"
template runValidTest(testName: string, identifier: untyped, num_blocks: int): untyped =
# We wrap the tests in a proc to avoid running out of globals
@ -88,17 +88,22 @@ suite "Official - Sanity - Blocks " & preset():
when const_preset=="minimal":
runValidTest("Empty epoch transition not finalizing", empty_epoch_transition_not_finalizing, 1)
# TODO investigate/fix after 0.9.0 transition broke this in mainnet
when false:
# TODO investigate/fix after 0.9.0 transition broke this in mainnet and
# in 0.9.1 even minimal broke. For the latter at least, it differs only
# in latest_block_header.body_root, which is just a hash_tree_root() of
# the one block read by this test case. All balances agree. It's an SSZ
# or hashing issue.
runValidTest("Attester slashing", attester_slashing, 1)
runValidTest("Proposer slashing", proposer_slashing, 1)
# TODO: Expected deposit in block
when false: # TODO: Assert .spec/crypto.nim(175, 14) `sig.kind == Real and pubkey.kind == Real`
runValidTest("Deposit in block", deposit_in_block, 1)
runValidTest("Deposit in block", deposit_in_block, 1)
runValidTest("Deposit top up", deposit_top_up, 1)
when false: # TODO: Assert spec/crypto.nim(156, 12) `x.kind == Real and other.kind == Real`
when const_preset=="minimal":
# TODO this doesn't work on mainnet
runValidTest("Attestation", attestation, 2)
runValidTest("Voluntary exit", voluntary_exit, 2)
runValidTest("Balance-driven status transitions", balance_driven_status_transitions, 1)

View File

@ -186,8 +186,7 @@ proc makeAttestation*(
aggregation_bits.raiseBit sac_index
let
msg = hash_tree_root(
AttestationDataAndCustodyBit(data: data, custody_bit: false))
msg = hash_tree_root(data)
sig =
if skipValidation notin flags:
bls_sign(
@ -195,20 +194,16 @@ proc makeAttestation*(
get_domain(
state,
DOMAIN_BEACON_ATTESTER,
compute_epoch_at_slot(state.slot)))
data.target.epoch))
else:
ValidatorSig()
Attestation(
data: data,
aggregation_bits: aggregation_bits,
signature: sig,
custody_bits: CommitteeValidatorsBits.init(committee.len)
signature: sig
)
proc makeTestDB*(tailState: BeaconState, tailBlock: BeaconBlock): BeaconChainDB =
let
tailRoot = signing_root(tailBlock)
result = init(BeaconChainDB, newMemoryDB())
BlockPool.preInit(result, tailState, tailBlock)