Merge branch 'dev' into vbuterin-patch-6
This commit is contained in:
commit
181dc183b6
|
@ -69,10 +69,10 @@ We require:
|
|||
G2_cofactor = 305502333931268344200999753193121504214466019254188142667664032982267604182971884026507427359259977847832272839041616661285803823378372096355777062779109
|
||||
q = 4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787
|
||||
|
||||
def hash_to_G2(message: bytes32, domain: uint64) -> [uint384]:
|
||||
def hash_to_G2(message_hash: Bytes32, domain: uint64) -> [uint384]:
|
||||
# Initial candidate x coordinate
|
||||
x_re = int.from_bytes(hash(message + bytes8(domain) + b'\x01'), 'big')
|
||||
x_im = int.from_bytes(hash(message + bytes8(domain) + b'\x02'), 'big')
|
||||
x_re = int.from_bytes(hash(message_hash + bytes8(domain) + b'\x01'), 'big')
|
||||
x_im = int.from_bytes(hash(message_hash + bytes8(domain) + b'\x02'), 'big')
|
||||
x_coordinate = Fq2([x_re, x_im]) # x = x_re + i * x_im
|
||||
|
||||
# Test candidate y coordinates until a one is found
|
||||
|
@ -128,17 +128,17 @@ g = Fq2([g_x, g_y])
|
|||
|
||||
### `bls_verify`
|
||||
|
||||
Let `bls_verify(pubkey: Bytes48, message: Bytes32, signature: Bytes96, domain: uint64) -> bool`:
|
||||
Let `bls_verify(pubkey: Bytes48, message_hash: Bytes32, signature: Bytes96, domain: uint64) -> bool`:
|
||||
|
||||
* Verify that `pubkey` is a valid G1 point.
|
||||
* Verify that `signature` is a valid G2 point.
|
||||
* Verify that `e(pubkey, hash_to_G2(message, domain)) == e(g, signature)`.
|
||||
* Verify that `e(pubkey, hash_to_G2(message_hash, domain)) == e(g, signature)`.
|
||||
|
||||
### `bls_verify_multiple`
|
||||
|
||||
Let `bls_verify_multiple(pubkeys: List[Bytes48], messages: List[Bytes32], signature: Bytes96, domain: uint64) -> bool`:
|
||||
Let `bls_verify_multiple(pubkeys: List[Bytes48], message_hashes: List[Bytes32], signature: Bytes96, domain: uint64) -> bool`:
|
||||
|
||||
* Verify that each `pubkey` in `pubkeys` is a valid G1 point.
|
||||
* Verify that `signature` is a valid G2 point.
|
||||
* Verify that `len(pubkeys)` equals `len(messages)` and denote the length `L`.
|
||||
* Verify that `e(pubkeys[0], hash_to_G2(messages[0], domain)) * ... * e(pubkeys[L-1], hash_to_G2(messages[L-1], domain)) == e(g, signature)`.
|
||||
* Verify that `len(pubkeys)` equals `len(message_hashes)` and denote the length `L`.
|
||||
* Verify that `e(pubkeys[0], hash_to_G2(message_hashes[0], domain)) * ... * e(pubkeys[L-1], hash_to_G2(message_hashes[L-1], domain)) == e(g, signature)`.
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
- [`get_epoch_start_slot`](#get_epoch_start_slot)
|
||||
- [`is_active_validator`](#is_active_validator)
|
||||
- [`get_active_validator_indices`](#get_active_validator_indices)
|
||||
- [`shuffle`](#shuffle)
|
||||
- [`get_permuted_index`](#get_permuted_index)
|
||||
- [`split`](#split)
|
||||
- [`get_epoch_committee_count`](#get_epoch_committee_count)
|
||||
- [`get_shuffling`](#get_shuffling)
|
||||
|
@ -77,6 +77,7 @@
|
|||
- [`get_attestation_participants`](#get_attestation_participants)
|
||||
- [`is_power_of_two`](#is_power_of_two)
|
||||
- [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-)
|
||||
- [`bytes_to_int`](#bytes_to_int)
|
||||
- [`get_effective_balance`](#get_effective_balance)
|
||||
- [`get_total_balance`](#get_total_balance)
|
||||
- [`get_fork_version`](#get_fork_version)
|
||||
|
@ -124,7 +125,7 @@
|
|||
- [Deposits](#deposits-1)
|
||||
- [Exits](#exits-1)
|
||||
- [Per-epoch processing](#per-epoch-processing)
|
||||
- [Helpers](#helpers)
|
||||
- [Helper variables](#helper-variables)
|
||||
- [Eth1 data](#eth1-data-1)
|
||||
- [Justification](#justification)
|
||||
- [Crosslinks](#crosslinks)
|
||||
|
@ -184,6 +185,7 @@ Code snippets appearing in `this style` are to be interpreted as Python code. Be
|
|||
| `BEACON_CHAIN_SHARD_NUMBER` | `2**64 - 1` | - |
|
||||
| `MAX_INDICES_PER_SLASHABLE_VOTE` | `2**12` (= 4,096) | votes |
|
||||
| `MAX_WITHDRAWALS_PER_EPOCH` | `2**2` (= 4) | withdrawals |
|
||||
| `SHUFFLE_ROUND_COUNT` | 90 | - |
|
||||
|
||||
* For the safety of crosslinks `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `EPOCH_LENGTH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
|
||||
|
||||
|
@ -362,8 +364,8 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git
|
|||
'epoch_boundary_root': 'bytes32',
|
||||
# Shard block's hash of root
|
||||
'shard_block_root': 'bytes32',
|
||||
# Last crosslink's hash of root
|
||||
'latest_crosslink_root': 'bytes32',
|
||||
# Last crosslink
|
||||
'latest_crosslink': Crosslink,
|
||||
# Last justified epoch in the beacon state
|
||||
'justified_epoch': 'uint64',
|
||||
# Hash of the last justified beacon block
|
||||
|
@ -524,6 +526,7 @@ The following data structures are defined as [SimpleSerialize (SSZ)](https://git
|
|||
# Ethereum 1.0 chain data
|
||||
'latest_eth1_data': Eth1Data,
|
||||
'eth1_data_votes': [Eth1DataVote],
|
||||
'deposit_index': 'uint64'
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -656,9 +659,10 @@ def get_previous_epoch(state: BeaconState) -> EpochNumber:
|
|||
Return the previous epoch of the given ``state``.
|
||||
If the current epoch is ``GENESIS_EPOCH``, return ``GENESIS_EPOCH``.
|
||||
"""
|
||||
if slot_to_epoch(state.slot) > GENESIS_EPOCH:
|
||||
return slot_to_epoch(state.slot) - 1
|
||||
return slot_to_epoch(state.slot)
|
||||
current_epoch = get_current_epoch(state)
|
||||
if current_epoch == GENESIS_EPOCH:
|
||||
return GENESIS_EPOCH
|
||||
return current_epoch - 1
|
||||
```
|
||||
|
||||
### `get_current_epoch`
|
||||
|
@ -700,57 +704,27 @@ def get_active_validator_indices(validators: List[Validator], epoch: EpochNumber
|
|||
return [i for i, v in enumerate(validators) if is_active_validator(v, epoch)]
|
||||
```
|
||||
|
||||
### `shuffle`
|
||||
### `get_permuted_index`
|
||||
|
||||
```python
|
||||
def shuffle(values: List[Any], seed: Bytes32) -> List[Any]:
|
||||
def get_permuted_index(index: int, list_size: int, seed: Bytes32) -> int:
|
||||
"""
|
||||
Return the shuffled ``values`` with ``seed`` as entropy.
|
||||
Return `p(index)` in a pseudorandom permutation `p` of `0...list_size-1` with ``seed`` as entropy.
|
||||
|
||||
Utilizes 'swap or not' shuffling found in
|
||||
https://link.springer.com/content/pdf/10.1007%2F978-3-642-32009-5_1.pdf
|
||||
See the 'generalized domain' algorithm on page 3.
|
||||
"""
|
||||
values_count = len(values)
|
||||
for round in range(SHUFFLE_ROUND_COUNT):
|
||||
pivot = bytes_to_int(hash(seed + int_to_bytes1(round))[0:8]) % list_size
|
||||
flip = (pivot - index) % list_size
|
||||
position = max(index, flip)
|
||||
source = hash(seed + int_to_bytes1(round) + int_to_bytes4(position // 256))
|
||||
byte = source[(position % 256) // 8]
|
||||
bit = (byte >> (position % 8)) % 2
|
||||
index = flip if bit else index
|
||||
|
||||
# Entropy is consumed from the seed in 3-byte (24 bit) chunks.
|
||||
rand_bytes = 3
|
||||
# The highest possible result of the RNG.
|
||||
rand_max = 2 ** (rand_bytes * 8) - 1
|
||||
|
||||
# The range of the RNG places an upper-bound on the size of the list that
|
||||
# may be shuffled. It is a logic error to supply an oversized list.
|
||||
assert values_count < rand_max
|
||||
|
||||
output = [x for x in values]
|
||||
source = seed
|
||||
index = 0
|
||||
while index < values_count - 1:
|
||||
# Re-hash the `source` to obtain a new pattern of bytes.
|
||||
source = hash(source)
|
||||
# Iterate through the `source` bytes in 3-byte chunks.
|
||||
for position in range(0, 32 - (32 % rand_bytes), rand_bytes):
|
||||
# Determine the number of indices remaining in `values` and exit
|
||||
# once the last index is reached.
|
||||
remaining = values_count - index
|
||||
if remaining == 1:
|
||||
break
|
||||
|
||||
# Read 3-bytes of `source` as a 24-bit big-endian integer.
|
||||
sample_from_source = int.from_bytes(source[position:position + rand_bytes], 'big')
|
||||
|
||||
# Sample values greater than or equal to `sample_max` will cause
|
||||
# modulo bias when mapped into the `remaining` range.
|
||||
sample_max = rand_max - rand_max % remaining
|
||||
|
||||
# Perform a swap if the consumed entropy will not cause modulo bias.
|
||||
if sample_from_source < sample_max:
|
||||
# Select a replacement index for the current index.
|
||||
replacement_position = (sample_from_source % remaining) + index
|
||||
# Swap the current index with the replacement index.
|
||||
output[index], output[replacement_position] = output[replacement_position], output[index]
|
||||
index += 1
|
||||
else:
|
||||
# The sample causes modulo bias. A new sample should be read.
|
||||
pass
|
||||
|
||||
return output
|
||||
return index
|
||||
```
|
||||
|
||||
### `split`
|
||||
|
@ -800,7 +774,10 @@ def get_shuffling(seed: Bytes32,
|
|||
committees_per_epoch = get_epoch_committee_count(len(active_validator_indices))
|
||||
|
||||
# Shuffle
|
||||
shuffled_active_validator_indices = shuffle(active_validator_indices, seed)
|
||||
shuffled_active_validator_indices = [
|
||||
active_validator_indices[get_permuted_index(i, len(active_validator_indices), seed)]
|
||||
for i in active_validator_indices
|
||||
]
|
||||
|
||||
# Split the shuffled list into committees_per_epoch pieces
|
||||
return split(shuffled_active_validator_indices, committees_per_epoch)
|
||||
|
@ -857,7 +834,7 @@ def get_next_epoch_committee_count(state: BeaconState) -> int:
|
|||
```python
|
||||
def get_crosslink_committees_at_slot(state: BeaconState,
|
||||
slot: SlotNumber,
|
||||
registry_change=False: bool) -> List[Tuple[List[ValidatorIndex], ShardNumber]]:
|
||||
registry_change: bool=False) -> List[Tuple[List[ValidatorIndex], ShardNumber]]:
|
||||
"""
|
||||
Return the list of ``(committee, shard)`` tuples for the ``slot``.
|
||||
|
||||
|
@ -1037,7 +1014,14 @@ def is_power_of_two(value: int) -> bool:
|
|||
|
||||
### `int_to_bytes1`, `int_to_bytes2`, ...
|
||||
|
||||
`int_to_bytes1(x): return x.to_bytes(1, 'big')`, `int_to_bytes2(x): return x.to_bytes(2, 'big')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32, 48, 96.
|
||||
`int_to_bytes1(x): return x.to_bytes(1, 'little')`, `int_to_bytes2(x): return x.to_bytes(2, 'little')`, and so on for all integers, particularly 1, 2, 3, 4, 8, 32, 48, 96.
|
||||
|
||||
### `bytes_to_int`
|
||||
|
||||
```python
|
||||
def bytes_to_int(data: bytes) -> int:
|
||||
return int.from_bytes(data, 'little')
|
||||
```
|
||||
|
||||
### `get_effective_balance`
|
||||
|
||||
|
@ -1150,7 +1134,7 @@ def verify_slashable_attestation(state: BeaconState, slashable_attestation: Slas
|
|||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_0_indices]),
|
||||
bls_aggregate_pubkeys([state.validator_registry[i].pubkey for i in custody_bit_1_indices]),
|
||||
],
|
||||
messages=[
|
||||
message_hashes=[
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=slashable_attestation.data, custody_bit=0b0)),
|
||||
hash_tree_root(AttestationDataAndCustodyBit(data=slashable_attestation.data, custody_bit=0b1)),
|
||||
],
|
||||
|
@ -1245,7 +1229,7 @@ def validate_proof_of_possession(state: BeaconState,
|
|||
|
||||
return bls_verify(
|
||||
pubkey=pubkey,
|
||||
message=hash_tree_root(proof_of_possession_data),
|
||||
message_hash=hash_tree_root(proof_of_possession_data),
|
||||
signature=proof_of_possession,
|
||||
domain=get_domain(
|
||||
state.fork,
|
||||
|
@ -1270,13 +1254,16 @@ def process_deposit(state: BeaconState,
|
|||
Note that this function mutates ``state``.
|
||||
"""
|
||||
# Validate the given `proof_of_possession`
|
||||
assert validate_proof_of_possession(
|
||||
proof_is_valid = validate_proof_of_possession(
|
||||
state,
|
||||
pubkey,
|
||||
proof_of_possession,
|
||||
withdrawal_credentials,
|
||||
)
|
||||
|
||||
if not proof_is_valid:
|
||||
return
|
||||
|
||||
validator_pubkeys = [v.pubkey for v in state.validator_registry]
|
||||
|
||||
if pubkey not in validator_pubkeys:
|
||||
|
@ -1495,6 +1482,7 @@ def get_initial_beacon_state(initial_validator_deposits: List[Deposit],
|
|||
# Ethereum 1.0 chain data
|
||||
latest_eth1_data=latest_eth1_data,
|
||||
eth1_data_votes=[],
|
||||
deposit_index=len(initial_validator_deposits)
|
||||
)
|
||||
|
||||
# Process initial deposits
|
||||
|
@ -1617,7 +1605,7 @@ Below are the processing steps that happen at every slot.
|
|||
|
||||
#### Block roots
|
||||
|
||||
* Let `previous_block_root` be the `tree_hash_root` of the previous beacon block processed in the chain.
|
||||
* Let `previous_block_root` be the `hash_tree_root` of the previous beacon block processed in the chain.
|
||||
* Set `state.latest_block_roots[(state.slot - 1) % LATEST_BLOCK_ROOTS_LENGTH] = previous_block_root`.
|
||||
* If `state.slot % LATEST_BLOCK_ROOTS_LENGTH == 0` append `merkle_root(state.latest_block_roots)` to `state.batched_block_roots`.
|
||||
|
||||
|
@ -1633,12 +1621,12 @@ Below are the processing steps that happen at every `block`.
|
|||
|
||||
* Let `block_without_signature_root` be the `hash_tree_root` of `block` where `block.signature` is set to `EMPTY_SIGNATURE`.
|
||||
* Let `proposal_root = hash_tree_root(ProposalSignedData(state.slot, BEACON_CHAIN_SHARD_NUMBER, block_without_signature_root))`.
|
||||
* Verify that `bls_verify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, message=proposal_root, signature=block.signature, domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_PROPOSAL))`.
|
||||
* Verify that `bls_verify(pubkey=state.validator_registry[get_beacon_proposer_index(state, state.slot)].pubkey, message_hash=proposal_root, signature=block.signature, domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_PROPOSAL))`.
|
||||
|
||||
#### RANDAO
|
||||
|
||||
* Let `proposer = state.validator_registry[get_beacon_proposer_index(state, state.slot)]`.
|
||||
* Verify that `bls_verify(pubkey=proposer.pubkey, message=int_to_bytes32(get_current_epoch(state)), signature=block.randao_reveal, domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))`.
|
||||
* Verify that `bls_verify(pubkey=proposer.pubkey, message_hash=int_to_bytes32(get_current_epoch(state)), signature=block.randao_reveal, domain=get_domain(state.fork, get_current_epoch(state), DOMAIN_RANDAO))`.
|
||||
* Set `state.latest_randao_mixes[get_current_epoch(state) % LATEST_RANDAO_MIXES_LENGTH] = xor(get_randao_mix(state, get_current_epoch(state)), hash(block.randao_reveal))`.
|
||||
|
||||
#### Eth1 data
|
||||
|
@ -1659,8 +1647,8 @@ For each `proposer_slashing` in `block.body.proposer_slashings`:
|
|||
* Verify that `proposer_slashing.proposal_data_1.shard == proposer_slashing.proposal_data_2.shard`.
|
||||
* Verify that `proposer_slashing.proposal_data_1.block_root != proposer_slashing.proposal_data_2.block_root`.
|
||||
* Verify that `proposer.penalized_epoch > get_current_epoch(state)`.
|
||||
* Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_1), signature=proposer_slashing.proposal_signature_1, domain=get_domain(state.fork, slot_to_epoch(proposer_slashing.proposal_data_1.slot), DOMAIN_PROPOSAL))`.
|
||||
* Verify that `bls_verify(pubkey=proposer.pubkey, message=hash_tree_root(proposer_slashing.proposal_data_2), signature=proposer_slashing.proposal_signature_2, domain=get_domain(state.fork, slot_to_epoch(proposer_slashing.proposal_data_2.slot), DOMAIN_PROPOSAL))`.
|
||||
* Verify that `bls_verify(pubkey=proposer.pubkey, message_hash=hash_tree_root(proposer_slashing.proposal_data_1), signature=proposer_slashing.proposal_signature_1, domain=get_domain(state.fork, slot_to_epoch(proposer_slashing.proposal_data_1.slot), DOMAIN_PROPOSAL))`.
|
||||
* Verify that `bls_verify(pubkey=proposer.pubkey, message_hash=hash_tree_root(proposer_slashing.proposal_data_2), signature=proposer_slashing.proposal_signature_2, domain=get_domain(state.fork, slot_to_epoch(proposer_slashing.proposal_data_2.slot), DOMAIN_PROPOSAL))`.
|
||||
* Run `penalize_validator(state, proposer_slashing.proposer_index)`.
|
||||
|
||||
##### Attester slashings
|
||||
|
@ -1688,7 +1676,7 @@ For each `attestation` in `block.body.attestations`:
|
|||
* Verify that `attestation.data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY < attestation.data.slot + EPOCH_LENGTH`.
|
||||
* Verify that `attestation.data.justified_epoch` is equal to `state.justified_epoch if attestation.data.slot >= get_epoch_start_slot(get_current_epoch(state)) else state.previous_justified_epoch`.
|
||||
* Verify that `attestation.data.justified_block_root` is equal to `get_block_root(state, get_epoch_start_slot(attestation.data.justified_epoch))`.
|
||||
* Verify that either `attestation.data.latest_crosslink_root` or `attestation.data.shard_block_root` equals `state.latest_crosslinks[attestation.data.shard].shard_block_root`.
|
||||
* Verify that either (i) `state.latest_crosslinks[attestation.data.shard] == attestation.data.latest_crosslink` or (ii) `state.latest_crosslinks[attestation.data.shard] == Crosslink(shard_block_root=attestation.data.shard_block_root, epoch=slot_to_epoch(attestation.data.slot))`.
|
||||
* Verify bitfields and aggregate signature:
|
||||
|
||||
```python
|
||||
|
@ -1734,6 +1722,7 @@ Verify that `len(block.body.deposits) <= MAX_DEPOSITS`.
|
|||
For each `deposit` in `block.body.deposits`:
|
||||
|
||||
* Let `serialized_deposit_data` be the serialized form of `deposit.deposit_data`. It should be 8 bytes for `deposit_data.amount` followed by 8 bytes for `deposit_data.timestamp` and then the `DepositInput` bytes. That is, it should match `deposit_data` in the [Ethereum 1.0 deposit contract](#ethereum-10-deposit-contract) of which the hash was placed into the Merkle tree.
|
||||
* Verify that `deposit.index == state.deposit_index`.
|
||||
* Verify that `verify_merkle_branch(hash(serialized_deposit_data), deposit.branch, DEPOSIT_CONTRACT_TREE_DEPTH, deposit.index, state.latest_eth1_data.deposit_root)` is `True`.
|
||||
|
||||
```python
|
||||
|
@ -1762,6 +1751,8 @@ process_deposit(
|
|||
)
|
||||
```
|
||||
|
||||
* Set `state.deposit_index += 1`.
|
||||
|
||||
##### Exits
|
||||
|
||||
Verify that `len(block.body.exits) <= MAX_EXITS`.
|
||||
|
@ -1772,14 +1763,14 @@ For each `exit` in `block.body.exits`:
|
|||
* Verify that `validator.exit_epoch > get_entry_exit_effect_epoch(get_current_epoch(state))`.
|
||||
* Verify that `get_current_epoch(state) >= exit.epoch`.
|
||||
* Let `exit_message = hash_tree_root(Exit(epoch=exit.epoch, validator_index=exit.validator_index, signature=EMPTY_SIGNATURE))`.
|
||||
* Verify that `bls_verify(pubkey=validator.pubkey, message=exit_message, signature=exit.signature, domain=get_domain(state.fork, exit.epoch, DOMAIN_EXIT))`.
|
||||
* Verify that `bls_verify(pubkey=validator.pubkey, message_hash=exit_message, signature=exit.signature, domain=get_domain(state.fork, exit.epoch, DOMAIN_EXIT))`.
|
||||
* Run `initiate_validator_exit(state, exit.validator_index)`.
|
||||
|
||||
### Per-epoch processing
|
||||
|
||||
The steps below happen when `(state.slot + 1) % EPOCH_LENGTH == 0`.
|
||||
|
||||
#### Helpers
|
||||
#### Helper variables
|
||||
|
||||
* Let `current_epoch = get_current_epoch(state)`.
|
||||
* Let `previous_epoch = get_previous_epoch(state)`.
|
||||
|
@ -1860,7 +1851,7 @@ Finally, update the following:
|
|||
|
||||
For every `slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch))`, let `crosslink_committees_at_slot = get_crosslink_committees_at_slot(state, slot)`. For every `(crosslink_committee, shard)` in `crosslink_committees_at_slot`, compute:
|
||||
|
||||
* Set `state.latest_crosslinks[shard] = Crosslink(epoch=current_epoch, shard_block_root=winning_root(crosslink_committee))` if `3 * total_attesting_balance(crosslink_committee) >= 2 * get_total_balance(crosslink_committee)`.
|
||||
* Set `state.latest_crosslinks[shard] = Crosslink(epoch=slot_to_epoch(slot), shard_block_root=winning_root(crosslink_committee))` if `3 * total_attesting_balance(crosslink_committee) >= 2 * get_total_balance(crosslink_committee)`.
|
||||
|
||||
#### Rewards and penalties
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ Phase 1 depends upon all of the constants defined in [Phase 0](0_beacon-chain.md
|
|||
|-------------------------------|------------------|--------|---------------|
|
||||
| `SHARD_CHUNK_SIZE` | 2**5 (= 32) | bytes | |
|
||||
| `SHARD_BLOCK_SIZE` | 2**14 (= 16,384) | bytes | |
|
||||
| `CROSSLINK_LOOKBACK` | 2**5 (= 32) | slots | |
|
||||
| `PERSISTENT_COMMITTEE_PERIOD` | 2**11 (= 2,048) | epochs | 9 days |
|
||||
|
||||
### Flags, domains, etc.
|
||||
|
@ -144,27 +145,40 @@ A node should sign a crosslink only if the following conditions hold. **If a nod
|
|||
|
||||
First, the conditions must recursively apply to the crosslink referenced in `last_crosslink_root` for the same shard (unless `last_crosslink_root` equals zero, in which case we are at the genesis).
|
||||
|
||||
Second, we verify the `shard_block_combined_data_root`. Let `h` be the slot _immediately after_ the slot of the shard block included by the last crosslink, and `h+n-1` be the slot number of the block directly referenced by the current `shard_block_root`. Let `B[i]` be the block at slot `h+i` in the shard chain. Let `bodies[0] .... bodies[n-1]` be the bodies of these blocks and `roots[0] ... roots[n-1]` the data roots. If there is a missing slot in the shard chain at position `h+i`, then `bodies[i] == b'\x00' * shard_block_maxbytes(state[i])` and `roots[i]` be the Merkle root of the empty data. Define `compute_merkle_root` be a simple Merkle root calculating function that takes as input a list of objects, where the list's length must be an exact power of two. We define the function for computing the combined data root as follows:
|
||||
Second, we verify the `shard_chain_commitment`.
|
||||
* Let `start_slot = state.latest_crosslinks[shard].epoch * EPOCH_LENGTH + EPOCH_LENGTH - CROSSLINK_LOOKBACK`.
|
||||
* Let `end_slot = attestation.data.slot - attestation.data.slot % EPOCH_LENGTH - CROSSLINK_LOOKBACK`.
|
||||
* Let `length = end_slot - start_slot`, `headers[0] .... headers[length-1]` be the serialized block headers in the canonical shard chain from the verifer's point of view (note that this implies that `headers` and `bodies` have been checked for validity).
|
||||
* Let `bodies[0] ... bodies[length-1]` be the bodies of the blocks.
|
||||
* Note: If there is a missing slot, then the header and body are the same as that of the block at the most recent slot that has a block.
|
||||
|
||||
We define two helpers:
|
||||
|
||||
```python
|
||||
ZERO_ROOT = merkle_root(bytes([0] * SHARD_BLOCK_SIZE))
|
||||
|
||||
def mk_combined_data_root(roots):
|
||||
data = roots + [ZERO_ROOT for _ in range(len(roots), next_power_of_2(len(roots)))]
|
||||
return compute_merkle_root(data)
|
||||
def pad_to_power_of_2(values: List[bytes]) -> List[bytes]:
|
||||
while not is_power_of_two(len(values)):
|
||||
values = values + [SHARD_BLOCK_SIZE]
|
||||
return values
|
||||
```
|
||||
|
||||
This outputs the root of a tree of the data roots, with the data roots all adjusted to have the same height if needed. The tree can also be viewed as a tree of all of the underlying data concatenated together, appropriately padded. Here is an equivalent definition that uses bodies instead of roots [TODO: check equivalence]:
|
||||
|
||||
```python
|
||||
def mk_combined_data_root(depths, bodies):
|
||||
data = b''.join(bodies)
|
||||
data += bytes([0] * (next_power_of_2(len(data)) - len(data))
|
||||
return compute_merkle_root([data[pos:pos+SHARD_CHUNK_SIZE] for pos in range(0, len(data), SHARD_CHUNK_SIZE)])
|
||||
def merkle_root_of_bytes(data: bytes) -> bytes:
|
||||
return merkle_root([data[i:i+32] for i in range(0, len(data), 32)])
|
||||
```
|
||||
|
||||
Verify that the `shard_block_combined_data_root` is the output of these functions.
|
||||
We define the function for computing the commitment as follows:
|
||||
|
||||
```python
|
||||
def compute_commitment(headers: List[ShardBlock], bodies: List[bytes]) -> Bytes32:
|
||||
return hash(
|
||||
merkle_root(pad_to_power_of_2([merkle_root_of_bytes(zpad(serialize(h), SHARD_BLOCK_SIZE)) for h in headers])),
|
||||
merkle_root(pad_to_power_of_2([merkle_root_of_bytes(h) for h in bodies]))
|
||||
)
|
||||
```
|
||||
|
||||
The `shard_chain_commitment` is only valid if it equals `compute_commitment(headers, bodies)`.
|
||||
|
||||
|
||||
### Shard block fork choice rule
|
||||
|
||||
The fork choice rule for any shard is LMD GHOST using the validators currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (ie. `state.crosslinks[shard].shard_block_root`). Only blocks whose `beacon_chain_ref` is the block in the main beacon chain at the specified `slot` should be considered (if the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than a slot).
|
||||
The fork choice rule for any shard is LMD GHOST using the shard chain attestations of the persistent committee and the beacon chain attestations of the crosslink committee currently assigned to that shard, but instead of being rooted in the genesis it is rooted in the block referenced in the most recent accepted crosslink (ie. `state.crosslinks[shard].shard_block_root`). Only blocks whose `beacon_chain_ref` is the block in the main beacon chain at the specified `slot` should be considered (if the beacon chain skips a slot, then the block at that slot is considered to be the block in the beacon chain at the highest slot lower than a slot).
|
||||
|
|
|
@ -42,7 +42,7 @@ __NOTICE__: This document is a work-in-progress for researchers and implementers
|
|||
- [Beacon block root](#beacon-block-root)
|
||||
- [Epoch boundary root](#epoch-boundary-root)
|
||||
- [Shard block root](#shard-block-root)
|
||||
- [Latest crosslink root](#latest-crosslink-root)
|
||||
- [Latest crosslink](#latest-crosslink)
|
||||
- [Justified epoch](#justified-epoch)
|
||||
- [Justified block root](#justified-block-root)
|
||||
- [Construct attestation](#construct-attestation)
|
||||
|
@ -166,7 +166,7 @@ Set `block.randao_reveal = epoch_signature` where `epoch_signature` is defined a
|
|||
```python
|
||||
epoch_signature = bls_sign(
|
||||
privkey=validator.privkey, # privkey store locally, not in state
|
||||
message=int_to_bytes32(slot_to_epoch(block.slot)),
|
||||
message_hash=int_to_bytes32(slot_to_epoch(block.slot)),
|
||||
domain=get_domain(
|
||||
fork=fork, # `fork` is the fork object at the slot `block.slot`
|
||||
epoch=slot_to_epoch(block.slot),
|
||||
|
@ -205,7 +205,7 @@ proposal_root = hash_tree_root(proposal_data)
|
|||
|
||||
signed_proposal_data = bls_sign(
|
||||
privkey=validator.privkey, # privkey store locally, not in state
|
||||
message=proposal_root,
|
||||
message_hash=proposal_root,
|
||||
domain=get_domain(
|
||||
fork=fork, # `fork` is the fork object at the slot `block.slot`
|
||||
epoch=slot_to_epoch(block.slot),
|
||||
|
@ -270,9 +270,9 @@ Set `attestation_data.shard_block_root = ZERO_HASH`.
|
|||
|
||||
_Note:_ This is a stub for phase 0.
|
||||
|
||||
##### Latest crosslink root
|
||||
##### Latest crosslink
|
||||
|
||||
Set `attestation_data.latest_crosslink_root = state.latest_crosslinks[shard].shard_block_root` where `state` is the beacon state at `head` and `shard` is the validator's assigned shard.
|
||||
Set `attestation_data.latest_crosslink = state.latest_crosslinks[shard]` where `state` is the beacon state at `head` and `shard` is the validator's assigned shard.
|
||||
|
||||
##### Justified epoch
|
||||
|
||||
|
@ -321,7 +321,7 @@ attestation_message_to_sign = hash_tree_root(attestation_data_and_custody_bit)
|
|||
|
||||
signed_attestation_data = bls_sign(
|
||||
privkey=validator.privkey, # privkey store locally, not in state
|
||||
message=attestation_message_to_sign,
|
||||
message_hash=attestation_message_to_sign,
|
||||
domain=get_domain(
|
||||
fork=fork, # `fork` is the fork object at the slot, `attestation_data.slot`
|
||||
epoch=slot_to_epoch(attestation_data.slot),
|
||||
|
@ -341,27 +341,47 @@ There are three possibilities for the shuffling at the next epoch:
|
|||
|
||||
Either (2) or (3) occurs if (1) fails. The choice between (2) and (3) is deterministic based upon `epochs_since_last_registry_update`.
|
||||
|
||||
`get_crosslink_committees_at_slot` is designed to be able to query slots in the next epoch. When querying slots in the next epoch there are two options -- with and without a `registry_change` -- which is the optional third parameter of the function. The following helper can be used to get the potential crosslink committees in the next epoch for a given `validator_index`. This function returns a list of 2 shard committee tuples.
|
||||
`get_crosslink_committees_at_slot` is designed to be able to query slots in the next epoch. When querying slots in the next epoch there are two options -- with and without a `registry_change` -- which is the optional third parameter of the function. The following helper can be used to get the potential crosslink committee assignments in the next epoch for a given `validator_index` and `registry_change`.
|
||||
|
||||
```python
|
||||
def get_next_epoch_crosslink_committees(state: BeaconState,
|
||||
validator_index: ValidatorIndex) -> List[Tuple[ValidatorIndex], ShardNumber]:
|
||||
def get_next_epoch_committee_assignment(
|
||||
state: BeaconState,
|
||||
validator_index: ValidatorIndex,
|
||||
registry_change: bool) -> Tuple[List[ValidatorIndex], ShardNumber, SlotNumber, bool]:
|
||||
"""
|
||||
Return the committee assignment in the next epoch for ``validator_index`` and ``registry_change``.
|
||||
``assignment`` returned is a tuple of the following form:
|
||||
* ``assignment[0]`` is the list of validators in the committee
|
||||
* ``assignment[1]`` is the shard to which the committee is assigned
|
||||
* ``assignment[2]`` is the slot at which the committee is assigned
|
||||
* ``assignment[3]`` is a bool signalling if the validator is expected to propose
|
||||
a beacon block at the assigned slot.
|
||||
"""
|
||||
current_epoch = get_current_epoch(state)
|
||||
next_epoch = current_epoch + 1
|
||||
next_epoch_start_slot = get_epoch_start_slot(next_epoch)
|
||||
potential_committees = []
|
||||
for validator_registry in [False, True]:
|
||||
for slot in range(next_epoch_start_slot, next_epoch_start_slot + EPOCH_LENGTH):
|
||||
shard_committees = get_crosslink_committees_at_slot(state, slot, validator_registry)
|
||||
selected_committees = [committee for committee in shard_committees if validator_index in committee[0]]
|
||||
if len(selected_committees) > 0:
|
||||
potential_assignments.append(selected_committees)
|
||||
break
|
||||
for slot in range(next_epoch_start_slot, next_epoch_start_slot + EPOCH_LENGTH):
|
||||
crosslink_committees = get_crosslink_committees_at_slot(
|
||||
state,
|
||||
slot,
|
||||
registry_change=registry_change,
|
||||
)
|
||||
selected_committees = [
|
||||
committee # Tuple[List[ValidatorIndex], ShardNumber]
|
||||
for committee in crosslink_committees
|
||||
if validator_index in committee[0]
|
||||
]
|
||||
if len(selected_committees) > 0:
|
||||
validators = selected_committees[0][0]
|
||||
shard = selected_committees[0][1]
|
||||
first_committee_at_slot = crosslink_committees[0][0] # List[ValidatorIndex]
|
||||
is_proposer = first_committee_at_slot[slot % len(first_committee_at_slot)] == validator_index
|
||||
|
||||
return potential_assignments
|
||||
assignment = (validators, shard, slot, is_proposer)
|
||||
return assignment
|
||||
```
|
||||
|
||||
`get_next_epoch_crosslink_committees` should be called at the beginning of each epoch to plan for the next epoch. A validator should always plan for both values of `registry_change` as a possibility unless the validator can concretely eliminate one of the options. Planning for a future shuffling involves noting at which slot one might have to attest and propose and also which shard one should begin syncing (in phase 1+).
|
||||
`get_next_epoch_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (slots during `current_epoch + 1`). A validator should always plan for assignments from both values of `registry_change` unless the validator can concretely eliminate one of the options. Planning for future assignments involves noting at which future slot one might have to attest and propose and also which shard one should begin syncing (in phase 1+).
|
||||
|
||||
## How to avoid slashing
|
||||
|
||||
|
|
Loading…
Reference in New Issue