Merge branch 'dev' into JustinDrake-patch-14
This commit is contained in:
commit
7a435d2e1a
|
@ -76,8 +76,7 @@
|
||||||
- [`generate_seed`](#generate_seed)
|
- [`generate_seed`](#generate_seed)
|
||||||
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
|
- [`get_beacon_proposer_index`](#get_beacon_proposer_index)
|
||||||
- [`verify_merkle_branch`](#verify_merkle_branch)
|
- [`verify_merkle_branch`](#verify_merkle_branch)
|
||||||
- [`get_crosslink_committee_for_attestation`](#get_crosslink_committee_for_attestation)
|
- [`get_attesting_indices`](#get_attesting_indices)
|
||||||
- [`get_attestation_participants`](#get_attestation_participants)
|
|
||||||
- [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-)
|
- [`int_to_bytes1`, `int_to_bytes2`, ...](#int_to_bytes1-int_to_bytes2-)
|
||||||
- [`bytes_to_int`](#bytes_to_int)
|
- [`bytes_to_int`](#bytes_to_int)
|
||||||
- [`get_effective_balance`](#get_effective_balance)
|
- [`get_effective_balance`](#get_effective_balance)
|
||||||
|
@ -176,7 +175,7 @@ These configurations are updated for releases, but may be out of sync during `de
|
||||||
| - | - |
|
| - | - |
|
||||||
| `SHARD_COUNT` | `2**10` (= 1,024) |
|
| `SHARD_COUNT` | `2**10` (= 1,024) |
|
||||||
| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) |
|
| `TARGET_COMMITTEE_SIZE` | `2**7` (= 128) |
|
||||||
| `MAX_ATTESTATION_PARTICIPANTS` | `2**12` (= 4,096) |
|
| `MAX_INDICES_PER_ATTESTATION` | `2**12` (= 4,096) |
|
||||||
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
|
| `MIN_PER_EPOCH_CHURN_LIMIT` | `2**2` (= 4) |
|
||||||
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
|
| `CHURN_LIMIT_QUOTIENT` | `2**16` (= 65,536) |
|
||||||
| `BASE_REWARDS_PER_EPOCH` | `4` |
|
| `BASE_REWARDS_PER_EPOCH` | `4` |
|
||||||
|
@ -301,6 +300,8 @@ The types are defined topologically to aid in facilitating an executable version
|
||||||
{
|
{
|
||||||
# Epoch number
|
# Epoch number
|
||||||
'epoch': 'uint64',
|
'epoch': 'uint64',
|
||||||
|
# Root of the previous crosslink
|
||||||
|
'previous_crosslink_root': 'bytes32',
|
||||||
# Shard data since the previous crosslink
|
# Shard data since the previous crosslink
|
||||||
'crosslink_data_root': 'bytes32',
|
'crosslink_data_root': 'bytes32',
|
||||||
}
|
}
|
||||||
|
@ -334,7 +335,7 @@ The types are defined topologically to aid in facilitating an executable version
|
||||||
|
|
||||||
# Crosslink vote
|
# Crosslink vote
|
||||||
'shard': 'uint64',
|
'shard': 'uint64',
|
||||||
'previous_crosslink': Crosslink,
|
'previous_crosslink_root': 'bytes32',
|
||||||
'crosslink_data_root': 'bytes32',
|
'crosslink_data_root': 'bytes32',
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -586,7 +587,8 @@ The types are defined topologically to aid in facilitating an executable version
|
||||||
'finalized_root': 'bytes32',
|
'finalized_root': 'bytes32',
|
||||||
|
|
||||||
# Recent state
|
# Recent state
|
||||||
'latest_crosslinks': [Crosslink, SHARD_COUNT],
|
'current_crosslinks': [Crosslink, SHARD_COUNT],
|
||||||
|
'previous_crosslinks': [Crosslink, SHARD_COUNT],
|
||||||
'latest_block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
'latest_block_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
||||||
'latest_state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
'latest_state_roots': ['bytes32', SLOTS_PER_HISTORICAL_ROOT],
|
||||||
'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH],
|
'latest_active_index_roots': ['bytes32', LATEST_ACTIVE_INDEX_ROOTS_LENGTH],
|
||||||
|
@ -1000,33 +1002,17 @@ def verify_merkle_branch(leaf: Bytes32, proof: List[Bytes32], depth: int, index:
|
||||||
return value == root
|
return value == root
|
||||||
```
|
```
|
||||||
|
|
||||||
### `get_crosslink_committee_for_attestation`
|
### `get_attesting_indices`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_crosslink_committee_for_attestation(state: BeaconState,
|
def get_attesting_indices(state: BeaconState,
|
||||||
attestation_data: AttestationData) -> List[ValidatorIndex]:
|
|
||||||
"""
|
|
||||||
Return the crosslink committee corresponding to ``attestation_data``.
|
|
||||||
"""
|
|
||||||
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)
|
|
||||||
|
|
||||||
# Find the committee in the list with the desired shard
|
|
||||||
assert attestation_data.shard in [shard for _, shard in crosslink_committees]
|
|
||||||
crosslink_committee = [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0]
|
|
||||||
|
|
||||||
return crosslink_committee
|
|
||||||
```
|
|
||||||
|
|
||||||
### `get_attestation_participants`
|
|
||||||
|
|
||||||
```python
|
|
||||||
def get_attestation_participants(state: BeaconState,
|
|
||||||
attestation_data: AttestationData,
|
attestation_data: AttestationData,
|
||||||
bitfield: bytes) -> List[ValidatorIndex]:
|
bitfield: bytes) -> List[ValidatorIndex]:
|
||||||
"""
|
"""
|
||||||
Return the sorted participant indices corresponding to ``attestation_data`` and ``bitfield``.
|
Return the sorted attesting indices corresponding to ``attestation_data`` and ``bitfield``.
|
||||||
"""
|
"""
|
||||||
crosslink_committee = get_crosslink_committee_for_attestation(state, attestation_data)
|
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)
|
||||||
|
crosslink_committee = [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0]
|
||||||
assert verify_bitfield(bitfield, len(crosslink_committee))
|
assert verify_bitfield(bitfield, len(crosslink_committee))
|
||||||
return sorted([index for i, index in enumerate(crosslink_committee) if get_bitfield_bit(bitfield, i) == 0b1])
|
return sorted([index for i, index in enumerate(crosslink_committee) if get_bitfield_bit(bitfield, i) == 0b1])
|
||||||
```
|
```
|
||||||
|
@ -1123,8 +1109,8 @@ def convert_to_indexed(state: BeaconState, attestation: Attestation) -> IndexedA
|
||||||
"""
|
"""
|
||||||
Convert ``attestation`` to (almost) indexed-verifiable form.
|
Convert ``attestation`` to (almost) indexed-verifiable form.
|
||||||
"""
|
"""
|
||||||
attesting_indices = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
attesting_indices = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
|
||||||
custody_bit_1_indices = get_attestation_participants(state, attestation.data, attestation.custody_bitfield)
|
custody_bit_1_indices = get_attesting_indices(state, attestation.data, attestation.custody_bitfield)
|
||||||
custody_bit_0_indices = [index for index in attesting_indices if index not in custody_bit_1_indices]
|
custody_bit_0_indices = [index for index in attesting_indices if index not in custody_bit_1_indices]
|
||||||
|
|
||||||
return IndexedAttestation(
|
return IndexedAttestation(
|
||||||
|
@ -1145,14 +1131,13 @@ def verify_indexed_attestation(state: BeaconState, indexed_attestation: IndexedA
|
||||||
custody_bit_0_indices = indexed_attestation.custody_bit_0_indices
|
custody_bit_0_indices = indexed_attestation.custody_bit_0_indices
|
||||||
custody_bit_1_indices = indexed_attestation.custody_bit_1_indices
|
custody_bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||||
|
|
||||||
# ensure no duplicate indices across custody bits
|
# Ensure no duplicate indices across custody bits
|
||||||
assert len(set(custody_bit_0_indices).intersection(set(custody_bit_1_indices))) == 0
|
assert len(set(custody_bit_0_indices).intersection(set(custody_bit_1_indices))) == 0
|
||||||
|
|
||||||
if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1]
|
if len(custody_bit_1_indices) > 0: # [TO BE REMOVED IN PHASE 1]
|
||||||
return False
|
return False
|
||||||
|
|
||||||
total_attesting_indices = len(custody_bit_0_indices) + len(custody_bit_1_indices)
|
if not (1 <= len(custody_bit_0_indices) + len(custody_bit_1_indices) <= MAX_INDICES_PER_ATTESTATION):
|
||||||
if not (1 <= total_attesting_indices <= MAX_ATTESTATION_PARTICIPANTS):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if custody_bit_0_indices != sorted(custody_bit_0_indices):
|
if custody_bit_0_indices != sorted(custody_bit_0_indices):
|
||||||
|
@ -1442,7 +1427,8 @@ def get_genesis_beacon_state(genesis_validator_deposits: List[Deposit],
|
||||||
finalized_root=ZERO_HASH,
|
finalized_root=ZERO_HASH,
|
||||||
|
|
||||||
# Recent state
|
# Recent state
|
||||||
latest_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
|
current_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
|
||||||
|
previous_crosslinks=Vector([Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH) for _ in range(SHARD_COUNT)]),
|
||||||
latest_block_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
|
latest_block_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
|
||||||
latest_state_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
|
latest_state_roots=Vector([ZERO_HASH for _ in range(SLOTS_PER_HISTORICAL_ROOT)]),
|
||||||
latest_active_index_roots=Vector([ZERO_HASH for _ in range(LATEST_ACTIVE_INDEX_ROOTS_LENGTH)]),
|
latest_active_index_roots=Vector([ZERO_HASH for _ in range(LATEST_ACTIVE_INDEX_ROOTS_LENGTH)]),
|
||||||
|
@ -1635,7 +1621,7 @@ def get_previous_epoch_matching_head_attestations(state: BeaconState) -> List[Pe
|
||||||
def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]:
|
def get_unslashed_attesting_indices(state: BeaconState, attestations: List[PendingAttestation]) -> List[ValidatorIndex]:
|
||||||
output = set()
|
output = set()
|
||||||
for a in attestations:
|
for a in attestations:
|
||||||
output = output.union(get_attestation_participants(state, a.data, a.aggregation_bitfield))
|
output = output.union(get_attesting_indices(state, a.data, a.aggregation_bitfield))
|
||||||
return sorted(filter(lambda index: not state.validator_registry[index].slashed, list(output)))
|
return sorted(filter(lambda index: not state.validator_registry[index].slashed, list(output)))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1645,31 +1631,40 @@ def get_attesting_balance(state: BeaconState, attestations: List[PendingAttestat
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_winning_root_and_participants(state: BeaconState, shard: Shard) -> Tuple[Bytes32, List[ValidatorIndex]]:
|
def get_crosslink_from_attestation_data(state: BeaconState, data: AttestationData) -> Crosslink:
|
||||||
all_attestations = state.current_epoch_attestations + state.previous_epoch_attestations
|
return Crosslink(
|
||||||
valid_attestations = [
|
epoch=min(slot_to_epoch(data.slot), state.current_crosslinks[data.shard].epoch + MAX_CROSSLINK_EPOCHS),
|
||||||
a for a in all_attestations if a.data.previous_crosslink == state.latest_crosslinks[shard]
|
previous_crosslink_root=data.previous_crosslink_root,
|
||||||
|
crosslink_data_root=data.crosslink_data_root,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
```python
|
||||||
|
def get_winning_crosslink_and_attesting_indices(state: BeaconState, epoch: Epoch, shard: Shard) -> Tuple[Crosslink, List[ValidatorIndex]]:
|
||||||
|
pending_attestations = state.current_epoch_attestations if epoch == get_current_epoch(state) else state.previous_epoch_attestations
|
||||||
|
shard_attestations = [a for a in pending_attestations if a.data.shard == shard]
|
||||||
|
shard_crosslinks = [get_crosslink_from_attestation_data(state, a.data) for a in shard_attestations]
|
||||||
|
candidate_crosslinks = [
|
||||||
|
c for c in shard_crosslinks
|
||||||
|
if hash_tree_root(state.current_crosslinks[shard]) in (c.previous_crosslink_root, hash_tree_root(c))
|
||||||
]
|
]
|
||||||
all_roots = [a.data.crosslink_data_root for a in valid_attestations]
|
if len(candidate_crosslinks) == 0:
|
||||||
|
return Crosslink(epoch=GENESIS_EPOCH, previous_crosslink_root=ZERO_HASH, crosslink_data_root=ZERO_HASH), []
|
||||||
|
|
||||||
# handle when no attestations for shard available
|
def get_attestations_for(crosslink: Crosslink) -> List[PendingAttestation]:
|
||||||
if len(all_roots) == 0:
|
return [a for a in shard_attestations if get_crosslink_from_attestation_data(state, a.data) == crosslink]
|
||||||
return ZERO_HASH, []
|
# Winning crosslink has the crosslink data root with the most balance voting for it (ties broken lexicographically)
|
||||||
|
winning_crosslink = max(candidate_crosslinks, key=lambda crosslink: (
|
||||||
|
get_attesting_balance(state, get_attestations_for(crosslink)), crosslink.crosslink_data_root
|
||||||
|
))
|
||||||
|
|
||||||
def get_attestations_for(root) -> List[PendingAttestation]:
|
return winning_crosslink, get_unslashed_attesting_indices(state, get_attestations_for(winning_crosslink))
|
||||||
return [a for a in valid_attestations if a.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
|
|
||||||
winning_root = max(all_roots, key=lambda r: (get_attesting_balance(state, get_attestations_for(r)), r))
|
|
||||||
|
|
||||||
return winning_root, get_unslashed_attesting_indices(state, get_attestations_for(winning_root))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def get_earliest_attestation(state: BeaconState, attestations: List[PendingAttestation], index: ValidatorIndex) -> PendingAttestation:
|
def get_earliest_attestation(state: BeaconState, attestations: List[PendingAttestation], index: ValidatorIndex) -> PendingAttestation:
|
||||||
return min([
|
return min([
|
||||||
a for a in attestations if index in get_attestation_participants(state, a.data, a.aggregation_bitfield)
|
a for a in attestations if index in get_attesting_indices(state, a.data, a.aggregation_bitfield)
|
||||||
], key=lambda a: a.inclusion_slot)
|
], key=lambda a: a.inclusion_slot)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1727,18 +1722,14 @@ Run the following function:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
def process_crosslinks(state: BeaconState) -> None:
|
def process_crosslinks(state: BeaconState) -> None:
|
||||||
|
state.previous_crosslinks = [c for c in state.current_crosslinks]
|
||||||
previous_epoch = get_previous_epoch(state)
|
previous_epoch = get_previous_epoch(state)
|
||||||
next_epoch = get_current_epoch(state) + 1
|
next_epoch = get_current_epoch(state) + 1
|
||||||
for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)):
|
for slot in range(get_epoch_start_slot(previous_epoch), get_epoch_start_slot(next_epoch)):
|
||||||
for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot):
|
for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot):
|
||||||
winning_root, participants = get_winning_root_and_participants(state, shard)
|
winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard)
|
||||||
participating_balance = get_total_balance(state, participants)
|
if 3 * get_total_balance(state, attesting_indices) >= 2 * get_total_balance(state, crosslink_committee):
|
||||||
total_balance = get_total_balance(state, crosslink_committee)
|
state.current_crosslinks[shard] = winning_crosslink
|
||||||
if 3 * participating_balance >= 2 * total_balance:
|
|
||||||
state.latest_crosslinks[shard] = Crosslink(
|
|
||||||
epoch=min(slot_to_epoch(slot), state.latest_crosslinks[shard].epoch + MAX_CROSSLINK_EPOCHS),
|
|
||||||
crosslink_data_root=winning_root
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Rewards and penalties
|
#### Rewards and penalties
|
||||||
|
@ -1800,17 +1791,15 @@ def get_justification_and_finalization_deltas(state: BeaconState) -> Tuple[List[
|
||||||
def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
def get_crosslink_deltas(state: BeaconState) -> Tuple[List[Gwei], List[Gwei]]:
|
||||||
rewards = [0 for index in range(len(state.validator_registry))]
|
rewards = [0 for index in range(len(state.validator_registry))]
|
||||||
penalties = [0 for index in range(len(state.validator_registry))]
|
penalties = [0 for index in range(len(state.validator_registry))]
|
||||||
previous_epoch_start_slot = get_epoch_start_slot(get_previous_epoch(state))
|
for slot in range(get_epoch_start_slot(get_previous_epoch(state)), get_epoch_start_slot(get_current_epoch(state))):
|
||||||
current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
|
|
||||||
for slot in range(previous_epoch_start_slot, current_epoch_start_slot):
|
|
||||||
for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot):
|
for crosslink_committee, shard in get_crosslink_committees_at_slot(state, slot):
|
||||||
winning_root, participants = get_winning_root_and_participants(state, shard)
|
winning_crosslink, attesting_indices = get_winning_crosslink_and_attesting_indices(state, slot_to_epoch(slot), shard)
|
||||||
participating_balance = get_total_balance(state, participants)
|
attesting_balance = get_total_balance(state, attesting_indices)
|
||||||
total_balance = get_total_balance(state, crosslink_committee)
|
committee_balance = get_total_balance(state, crosslink_committee)
|
||||||
for index in crosslink_committee:
|
for index in crosslink_committee:
|
||||||
base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index)
|
base_reward = get_base_reward(state, get_previous_epoch_total_balance(state), index)
|
||||||
if index in participants:
|
if index in attesting_indices:
|
||||||
rewards[index] += base_reward * participating_balance // total_balance
|
rewards[index] += base_reward * attesting_balance // committee_balance
|
||||||
else:
|
else:
|
||||||
penalties[index] += base_reward
|
penalties[index] += base_reward
|
||||||
return [rewards, penalties]
|
return [rewards, penalties]
|
||||||
|
@ -2061,32 +2050,26 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
||||||
Process ``Attestation`` operation.
|
Process ``Attestation`` operation.
|
||||||
Note that this function mutates ``state``.
|
Note that this function mutates ``state``.
|
||||||
"""
|
"""
|
||||||
assert attestation.data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= attestation.data.slot + SLOTS_PER_EPOCH
|
data = attestation.data
|
||||||
|
min_slot = state.slot - SLOTS_PER_EPOCH if get_current_epoch(state) > GENESIS_EPOCH else GENESIS_SLOT
|
||||||
|
assert min_slot <= data.slot <= state.slot - MIN_ATTESTATION_INCLUSION_DELAY
|
||||||
|
|
||||||
# Check target epoch, source epoch, and source root
|
# Check target epoch, source epoch, source root, and source crosslink
|
||||||
target_epoch = slot_to_epoch(attestation.data.slot)
|
target_epoch = slot_to_epoch(data.slot)
|
||||||
assert (target_epoch, attestation.data.source_epoch, attestation.data.source_root) in {
|
assert (target_epoch, data.source_epoch, data.source_root, data.previous_crosslink_root) in {
|
||||||
(get_current_epoch(state), state.current_justified_epoch, state.current_justified_root),
|
(get_current_epoch(state), state.current_justified_epoch, state.current_justified_root, hash_tree_root(state.current_crosslinks[data.shard])),
|
||||||
(get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root),
|
(get_previous_epoch(state), state.previous_justified_epoch, state.previous_justified_root, hash_tree_root(state.previous_crosslinks[data.shard])),
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check crosslink data
|
# Check crosslink data root
|
||||||
assert attestation.data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1]
|
assert data.crosslink_data_root == ZERO_HASH # [to be removed in phase 1]
|
||||||
assert state.latest_crosslinks[attestation.data.shard] in {
|
|
||||||
attestation.data.previous_crosslink, # Case 1: latest crosslink matches previous crosslink
|
|
||||||
Crosslink( # Case 2: latest crosslink matches current crosslink
|
|
||||||
crosslink_data_root=attestation.data.crosslink_data_root,
|
|
||||||
epoch=min(slot_to_epoch(attestation.data.slot),
|
|
||||||
attestation.data.previous_crosslink.epoch + MAX_CROSSLINK_EPOCHS)
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check signature and bitfields
|
# Check signature and bitfields
|
||||||
assert verify_indexed_attestation(state, convert_to_indexed(state, attestation))
|
assert verify_indexed_attestation(state, convert_to_indexed(state, attestation))
|
||||||
|
|
||||||
# Cache pending attestation
|
# Cache pending attestation
|
||||||
pending_attestation = PendingAttestation(
|
pending_attestation = PendingAttestation(
|
||||||
data=attestation.data,
|
data=data,
|
||||||
aggregation_bitfield=attestation.aggregation_bitfield,
|
aggregation_bitfield=attestation.aggregation_bitfield,
|
||||||
inclusion_slot=state.slot
|
inclusion_slot=state.slot
|
||||||
)
|
)
|
||||||
|
|
|
@ -309,7 +309,7 @@ def process_chunk_challenge(state: BeaconState,
|
||||||
responder = state.validator_registry[challenge.responder_index]
|
responder = state.validator_registry[challenge.responder_index]
|
||||||
assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
|
assert responder.exit_epoch >= get_current_epoch(state) - MAX_CHUNK_CHALLENGE_DELAY
|
||||||
# Verify the responder participated in the attestation
|
# Verify the responder participated in the attestation
|
||||||
attesters = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
|
||||||
assert challenge.responder_index in attesters
|
assert challenge.responder_index in attesters
|
||||||
# Verify the challenge is not a duplicate
|
# Verify the challenge is not a duplicate
|
||||||
for record in state.custody_chunk_challenge_records:
|
for record in state.custody_chunk_challenge_records:
|
||||||
|
@ -361,7 +361,7 @@ def process_bit_challenge(state: BeaconState,
|
||||||
min_challengeable_epoch = responder.exit_epoch - EPOCHS_PER_CUSTODY_PERIOD * (1 + responder.max_reveal_lateness)
|
min_challengeable_epoch = responder.exit_epoch - EPOCHS_PER_CUSTODY_PERIOD * (1 + responder.max_reveal_lateness)
|
||||||
assert min_challengeable_epoch <= slot_to_epoch(challenge.attestation.data.slot)
|
assert min_challengeable_epoch <= slot_to_epoch(challenge.attestation.data.slot)
|
||||||
# Verify the responder participated in the attestation
|
# Verify the responder participated in the attestation
|
||||||
attesters = get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)
|
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)
|
||||||
assert challenge.responder_index in attesters
|
assert challenge.responder_index in attesters
|
||||||
# A validator can be the challenger or responder for at most one challenge at a time
|
# A validator can be the challenger or responder for at most one challenge at a time
|
||||||
for record in state.custody_bit_challenge_records:
|
for record in state.custody_bit_challenge_records:
|
||||||
|
|
|
@ -38,13 +38,13 @@ __NOTICE__: This document is a work-in-progress for researchers and implementers
|
||||||
- [Attestations](#attestations-1)
|
- [Attestations](#attestations-1)
|
||||||
- [Attestation data](#attestation-data)
|
- [Attestation data](#attestation-data)
|
||||||
- [Slot](#slot-1)
|
- [Slot](#slot-1)
|
||||||
- [Shard](#shard)
|
|
||||||
- [Beacon block root](#beacon-block-root)
|
- [Beacon block root](#beacon-block-root)
|
||||||
- [Target root](#target-root)
|
|
||||||
- [Crosslink data root](#crosslink-data-root)
|
|
||||||
- [Latest crosslink](#latest-crosslink)
|
|
||||||
- [Source epoch](#source-epoch)
|
- [Source epoch](#source-epoch)
|
||||||
- [Source root](#source-root)
|
- [Source root](#source-root)
|
||||||
|
- [Target root](#target-root)
|
||||||
|
- [Shard](#shard)
|
||||||
|
- [Previous crosslink root](#previous-crosslink-root)
|
||||||
|
- [Crosslink data root](#crosslink-data-root)
|
||||||
- [Construct attestation](#construct-attestation)
|
- [Construct attestation](#construct-attestation)
|
||||||
- [Data](#data)
|
- [Data](#data)
|
||||||
- [Aggregation bitfield](#aggregation-bitfield)
|
- [Aggregation bitfield](#aggregation-bitfield)
|
||||||
|
@ -250,14 +250,18 @@ First the validator should construct `attestation_data`, an [`AttestationData`](
|
||||||
|
|
||||||
Set `attestation_data.slot = head_state.slot`.
|
Set `attestation_data.slot = head_state.slot`.
|
||||||
|
|
||||||
##### Shard
|
|
||||||
|
|
||||||
Set `attestation_data.shard = shard` where `shard` is the shard associated with the validator's committee defined by `get_crosslink_committees_at_slot`.
|
|
||||||
|
|
||||||
##### Beacon block root
|
##### Beacon block root
|
||||||
|
|
||||||
Set `attestation_data.beacon_block_root = signing_root(head_block)`.
|
Set `attestation_data.beacon_block_root = signing_root(head_block)`.
|
||||||
|
|
||||||
|
##### Source epoch
|
||||||
|
|
||||||
|
Set `attestation_data.source_epoch = head_state.justified_epoch`.
|
||||||
|
|
||||||
|
##### Source root
|
||||||
|
|
||||||
|
Set `attestation_data.source_root = head_state.current_justified_root`.
|
||||||
|
|
||||||
##### Target root
|
##### Target root
|
||||||
|
|
||||||
Set `attestation_data.target_root = signing_root(epoch_boundary)` where `epoch_boundary` is the block at the most recent epoch boundary.
|
Set `attestation_data.target_root = signing_root(epoch_boundary)` where `epoch_boundary` is the block at the most recent epoch boundary.
|
||||||
|
@ -266,24 +270,20 @@ _Note:_ This can be looked up in the state using:
|
||||||
* Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`.
|
* Let `epoch_start_slot = get_epoch_start_slot(get_current_epoch(head_state))`.
|
||||||
* Set `epoch_boundary = head if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`.
|
* Set `epoch_boundary = head if epoch_start_slot == head_state.slot else get_block_root(state, epoch_start_slot)`.
|
||||||
|
|
||||||
|
##### Shard
|
||||||
|
|
||||||
|
Set `attestation_data.shard = shard` where `shard` is the shard associated with the validator's committee defined by `get_crosslink_committees_at_slot`.
|
||||||
|
|
||||||
|
##### Previous crosslink root
|
||||||
|
|
||||||
|
Set `attestation_data.previous_crosslink_root = hash_tree_root(head_state.current_crosslinks[shard])`.
|
||||||
|
|
||||||
##### Crosslink data root
|
##### Crosslink data root
|
||||||
|
|
||||||
Set `attestation_data.crosslink_data_root = ZERO_HASH`.
|
Set `attestation_data.crosslink_data_root = ZERO_HASH`.
|
||||||
|
|
||||||
_Note:_ This is a stub for phase 0.
|
_Note:_ This is a stub for phase 0.
|
||||||
|
|
||||||
##### Latest crosslink
|
|
||||||
|
|
||||||
Set `attestation_data.previous_crosslink = head_state.latest_crosslinks[shard]`.
|
|
||||||
|
|
||||||
##### Source epoch
|
|
||||||
|
|
||||||
Set `attestation_data.source_epoch = head_state.justified_epoch`.
|
|
||||||
|
|
||||||
##### Source root
|
|
||||||
|
|
||||||
Set `attestation_data.source_root = head_state.current_justified_root`.
|
|
||||||
|
|
||||||
#### Construct attestation
|
#### Construct attestation
|
||||||
|
|
||||||
Next the validator creates `attestation`, an [`Attestation`](../core/0_beacon-chain.md#attestation) object.
|
Next the validator creates `attestation`, an [`Attestation`](../core/0_beacon-chain.md#attestation) object.
|
||||||
|
@ -299,7 +299,7 @@ Set `attestation.data = attestation_data` where `attestation_data` is the `Attes
|
||||||
* Set `aggregation_bitfield[index_into_committee // 8] |= 2 ** (index_into_committee % 8)`.
|
* Set `aggregation_bitfield[index_into_committee // 8] |= 2 ** (index_into_committee % 8)`.
|
||||||
* Set `attestation.aggregation_bitfield = aggregation_bitfield`.
|
* Set `attestation.aggregation_bitfield = aggregation_bitfield`.
|
||||||
|
|
||||||
_Note_: Calling `get_attestation_participants(state, attestation.data, attestation.aggregation_bitfield)` should return a list of length equal to 1, containing `validator_index`.
|
_Note_: Calling `get_attesting_indices(state, attestation.data, attestation.aggregation_bitfield)` should return a list of length equal to 1, containing `validator_index`.
|
||||||
|
|
||||||
##### Custody bitfield
|
##### Custody bitfield
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from .hash_function import hash
|
from .hash_function import hash
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
BYTES_PER_CHUNK = 32
|
BYTES_PER_CHUNK = 32
|
||||||
BYTES_PER_LENGTH_PREFIX = 4
|
BYTES_PER_LENGTH_PREFIX = 4
|
||||||
|
@ -9,9 +10,10 @@ ZERO_CHUNK = b'\x00' * BYTES_PER_CHUNK
|
||||||
def SSZType(fields):
|
def SSZType(fields):
|
||||||
class SSZObject():
|
class SSZObject():
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
for f in fields:
|
for f, t in fields.items():
|
||||||
if f not in kwargs:
|
if f not in kwargs:
|
||||||
raise Exception("Missing constructor argument: %s" % f)
|
setattr(self, f, get_zero_value(t))
|
||||||
|
else:
|
||||||
setattr(self, f, kwargs[f])
|
setattr(self, f, kwargs[f])
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -58,18 +60,40 @@ class Vector():
|
||||||
|
|
||||||
|
|
||||||
def is_basic(typ):
|
def is_basic(typ):
|
||||||
return isinstance(typ, str) and (typ[:4] in ('uint', 'bool') or typ == 'byte')
|
# if not a string, it is a complex, and cannot be basic
|
||||||
|
if not isinstance(typ, str):
|
||||||
|
return False
|
||||||
|
# "uintN": N-bit unsigned integer (where N in [8, 16, 32, 64, 128, 256])
|
||||||
|
elif typ[:4] == 'uint' and typ[4:] in ['8', '16', '32', '64', '128', '256']:
|
||||||
|
return True
|
||||||
|
# "bool": True or False
|
||||||
|
elif typ == 'bool':
|
||||||
|
return True
|
||||||
|
# alias: "byte" -> "uint8"
|
||||||
|
elif typ == 'byte':
|
||||||
|
return True
|
||||||
|
# default
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_constant_sized(typ):
|
def is_constant_sized(typ):
|
||||||
|
# basic objects are fixed size by definition
|
||||||
if is_basic(typ):
|
if is_basic(typ):
|
||||||
return True
|
return True
|
||||||
|
# dynamic size array type, "list": [elem_type].
|
||||||
|
# Not constant size by definition.
|
||||||
elif isinstance(typ, list) and len(typ) == 1:
|
elif isinstance(typ, list) and len(typ) == 1:
|
||||||
return is_constant_sized(typ[0])
|
|
||||||
elif isinstance(typ, list) and len(typ) == 2:
|
|
||||||
return False
|
return False
|
||||||
|
# fixed size array type, "vector": [elem_type, length]
|
||||||
|
# Constant size, but only if the elements are.
|
||||||
|
elif isinstance(typ, list) and len(typ) == 2:
|
||||||
|
return is_constant_sized(typ[0])
|
||||||
|
# bytes array (fixed or dynamic size)
|
||||||
elif isinstance(typ, str) and typ[:5] == 'bytes':
|
elif isinstance(typ, str) and typ[:5] == 'bytes':
|
||||||
return len(typ) > 5
|
# if no length suffix, it has a dynamic size
|
||||||
|
return typ != 'bytes'
|
||||||
|
# containers are only constant-size if all of the fields are constant size.
|
||||||
elif hasattr(typ, 'fields'):
|
elif hasattr(typ, 'fields'):
|
||||||
for subtype in typ.fields.values():
|
for subtype in typ.fields.values():
|
||||||
if not is_constant_sized(subtype):
|
if not is_constant_sized(subtype):
|
||||||
|
@ -90,40 +114,98 @@ def coerce_to_bytes(x):
|
||||||
raise Exception("Expecting bytes")
|
raise Exception("Expecting bytes")
|
||||||
|
|
||||||
|
|
||||||
|
def encode_bytes(value):
|
||||||
|
serialized_bytes = coerce_to_bytes(value)
|
||||||
|
assert len(serialized_bytes) < 2 ** (8 * BYTES_PER_LENGTH_PREFIX)
|
||||||
|
serialized_length = len(serialized_bytes).to_bytes(BYTES_PER_LENGTH_PREFIX, 'little')
|
||||||
|
return serialized_length + serialized_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def encode_variable_size_container(values, types):
|
||||||
|
return encode_bytes(encode_fixed_size_container(values, types))
|
||||||
|
|
||||||
|
|
||||||
|
def encode_fixed_size_container(values, types):
|
||||||
|
return b''.join([serialize_value(v, typ) for (v, typ) in zip(values, types)])
|
||||||
|
|
||||||
|
|
||||||
def serialize_value(value, typ=None):
|
def serialize_value(value, typ=None):
|
||||||
if typ is None:
|
if typ is None:
|
||||||
typ = infer_type(value)
|
typ = infer_type(value)
|
||||||
|
# "uintN"
|
||||||
if isinstance(typ, str) and typ[:4] == 'uint':
|
if isinstance(typ, str) and typ[:4] == 'uint':
|
||||||
length = int(typ[4:])
|
length = int(typ[4:])
|
||||||
assert length in (8, 16, 32, 64, 128, 256)
|
assert length in (8, 16, 32, 64, 128, 256)
|
||||||
return value.to_bytes(length // 8, 'little')
|
return value.to_bytes(length // 8, 'little')
|
||||||
elif typ == 'bool':
|
# "bool"
|
||||||
|
elif isinstance(typ, str) and typ == 'bool':
|
||||||
assert value in (True, False)
|
assert value in (True, False)
|
||||||
return b'\x01' if value is True else b'\x00'
|
return b'\x01' if value is True else b'\x00'
|
||||||
elif (isinstance(typ, list) and len(typ) == 1) or typ == 'bytes':
|
# Vector
|
||||||
serialized_bytes = coerce_to_bytes(value) if typ == 'bytes' else b''.join([serialize_value(element, typ[0]) for element in value])
|
|
||||||
assert len(serialized_bytes) < 2**(8 * BYTES_PER_LENGTH_PREFIX)
|
|
||||||
serialized_length = len(serialized_bytes).to_bytes(BYTES_PER_LENGTH_PREFIX, 'little')
|
|
||||||
return serialized_length + serialized_bytes
|
|
||||||
elif isinstance(typ, list) and len(typ) == 2:
|
elif isinstance(typ, list) and len(typ) == 2:
|
||||||
|
# (regardless of element type, sanity-check if the length reported in the vector type matches the value length)
|
||||||
assert len(value) == typ[1]
|
assert len(value) == typ[1]
|
||||||
return b''.join([serialize_value(element, typ[0]) for element in value])
|
# If value is fixed-size (i.e. element type is fixed-size):
|
||||||
|
if is_constant_sized(typ):
|
||||||
|
return encode_fixed_size_container(value, [typ[0]] * len(value))
|
||||||
|
# If value is variable-size (i.e. element type is variable-size)
|
||||||
|
else:
|
||||||
|
return encode_variable_size_container(value, [typ[0]] * len(value))
|
||||||
|
# "bytes" (variable size)
|
||||||
|
elif isinstance(typ, str) and typ == 'bytes':
|
||||||
|
return encode_bytes(value)
|
||||||
|
# List
|
||||||
|
elif isinstance(typ, list) and len(typ) == 1:
|
||||||
|
return encode_variable_size_container(value, [typ[0]] * len(value))
|
||||||
|
# "bytesN" (fixed size)
|
||||||
elif isinstance(typ, str) and len(typ) > 5 and typ[:5] == 'bytes':
|
elif isinstance(typ, str) and len(typ) > 5 and typ[:5] == 'bytes':
|
||||||
assert len(value) == int(typ[5:]), (value, int(typ[5:]))
|
assert len(value) == int(typ[5:]), (value, int(typ[5:]))
|
||||||
return coerce_to_bytes(value)
|
return coerce_to_bytes(value)
|
||||||
|
# containers
|
||||||
elif hasattr(typ, 'fields'):
|
elif hasattr(typ, 'fields'):
|
||||||
serialized_bytes = b''.join([serialize_value(getattr(value, field), subtype) for field, subtype in typ.fields.items()])
|
values = [getattr(value, field) for field in typ.fields.keys()]
|
||||||
|
types = list(typ.fields.values())
|
||||||
if is_constant_sized(typ):
|
if is_constant_sized(typ):
|
||||||
return serialized_bytes
|
return encode_fixed_size_container(values, types)
|
||||||
else:
|
else:
|
||||||
assert len(serialized_bytes) < 2**(8 * BYTES_PER_LENGTH_PREFIX)
|
return encode_variable_size_container(values, types)
|
||||||
serialized_length = len(serialized_bytes).to_bytes(BYTES_PER_LENGTH_PREFIX, 'little')
|
|
||||||
return serialized_length + serialized_bytes
|
|
||||||
else:
|
else:
|
||||||
print(value, typ)
|
print(value, typ)
|
||||||
raise Exception("Type not recognized")
|
raise Exception("Type not recognized")
|
||||||
|
|
||||||
|
|
||||||
|
def get_zero_value(typ: Any) -> Any:
|
||||||
|
if isinstance(typ, str):
|
||||||
|
# Bytes array
|
||||||
|
if typ == 'bytes':
|
||||||
|
return b''
|
||||||
|
# bytesN
|
||||||
|
elif typ[:5] == 'bytes' and len(typ) > 5:
|
||||||
|
length = int(typ[5:])
|
||||||
|
return b'\x00' * length
|
||||||
|
# Basic types
|
||||||
|
elif typ == 'bool':
|
||||||
|
return False
|
||||||
|
elif typ[:4] == 'uint':
|
||||||
|
return 0
|
||||||
|
elif typ == 'byte':
|
||||||
|
return 0x00
|
||||||
|
else:
|
||||||
|
raise ValueError("Type not recognized")
|
||||||
|
# Vector:
|
||||||
|
elif isinstance(typ, list) and len(typ) == 2:
|
||||||
|
return [get_zero_value(typ[0]) for _ in range(typ[1])]
|
||||||
|
# List:
|
||||||
|
elif isinstance(typ, list) and len(typ) == 1:
|
||||||
|
return []
|
||||||
|
# Container:
|
||||||
|
elif hasattr(typ, 'fields'):
|
||||||
|
return typ(**{field: get_zero_value(subtype) for field, subtype in typ.fields.items()})
|
||||||
|
else:
|
||||||
|
print(typ)
|
||||||
|
raise Exception("Type not recognized")
|
||||||
|
|
||||||
|
|
||||||
def chunkify(bytez):
|
def chunkify(bytez):
|
||||||
bytez += b'\x00' * (-len(bytez) % BYTES_PER_CHUNK)
|
bytez += b'\x00' * (-len(bytez) % BYTES_PER_CHUNK)
|
||||||
return [bytez[i:i + 32] for i in range(0, len(bytez), 32)]
|
return [bytez[i:i + 32] for i in range(0, len(bytez), 32)]
|
||||||
|
@ -152,12 +234,27 @@ def mix_in_length(root, length):
|
||||||
|
|
||||||
|
|
||||||
def infer_type(value):
|
def infer_type(value):
|
||||||
|
"""
|
||||||
|
Note: defaults to uint64 for integer type inference due to lack of information.
|
||||||
|
Other integer sizes are still supported, see spec.
|
||||||
|
:param value: The value to infer a SSZ type for.
|
||||||
|
:return: The SSZ type.
|
||||||
|
"""
|
||||||
if hasattr(value.__class__, 'fields'):
|
if hasattr(value.__class__, 'fields'):
|
||||||
return value.__class__
|
return value.__class__
|
||||||
elif isinstance(value, Vector):
|
elif isinstance(value, Vector):
|
||||||
return [infer_type(value[0]) if len(value) > 0 else 'uint64', len(value)]
|
if len(value) > 0:
|
||||||
|
return [infer_type(value[0]), len(value)]
|
||||||
|
else:
|
||||||
|
# Element type does not matter too much,
|
||||||
|
# assumed to be a basic type for size-encoding purposes, vector is empty.
|
||||||
|
return ['uint64']
|
||||||
elif isinstance(value, list):
|
elif isinstance(value, list):
|
||||||
return [infer_type(value[0])] if len(value) > 0 else ['uint64']
|
if len(value) > 0:
|
||||||
|
return [infer_type(value[0])]
|
||||||
|
else:
|
||||||
|
# Element type does not matter, list-content size will be encoded regardless, list is empty.
|
||||||
|
return ['uint64']
|
||||||
elif isinstance(value, (bytes, str)):
|
elif isinstance(value, (bytes, str)):
|
||||||
return 'bytes'
|
return 'bytes'
|
||||||
elif isinstance(value, int):
|
elif isinstance(value, int):
|
||||||
|
@ -169,24 +266,41 @@ def infer_type(value):
|
||||||
def hash_tree_root(value, typ=None):
|
def hash_tree_root(value, typ=None):
|
||||||
if typ is None:
|
if typ is None:
|
||||||
typ = infer_type(value)
|
typ = infer_type(value)
|
||||||
|
# -------------------------------------
|
||||||
|
# merkleize(pack(value))
|
||||||
|
# basic object: merkleize packed version (merkleization pads it to 32 bytes if it is not already)
|
||||||
if is_basic(typ):
|
if is_basic(typ):
|
||||||
return merkleize(pack([value], typ))
|
return merkleize(pack([value], typ))
|
||||||
elif isinstance(typ, list) and len(typ) == 1 and is_basic(typ[0]):
|
# or a vector of basic objects
|
||||||
return mix_in_length(merkleize(pack(value, typ[0])), len(value))
|
|
||||||
elif isinstance(typ, list) and len(typ) == 1 and not is_basic(typ[0]):
|
|
||||||
return mix_in_length(merkleize([hash_tree_root(element, typ[0]) for element in value]), len(value))
|
|
||||||
elif isinstance(typ, list) and len(typ) == 2 and is_basic(typ[0]):
|
elif isinstance(typ, list) and len(typ) == 2 and is_basic(typ[0]):
|
||||||
assert len(value) == typ[1]
|
assert len(value) == typ[1]
|
||||||
return merkleize(pack(value, typ[0]))
|
return merkleize(pack(value, typ[0]))
|
||||||
|
# -------------------------------------
|
||||||
|
# mix_in_length(merkleize(pack(value)), len(value))
|
||||||
|
# if value is a list of basic objects
|
||||||
|
elif isinstance(typ, list) and len(typ) == 1 and is_basic(typ[0]):
|
||||||
|
return mix_in_length(merkleize(pack(value, typ[0])), len(value))
|
||||||
|
# (needs some extra work for non-fixed-sized bytes array)
|
||||||
elif typ == 'bytes':
|
elif typ == 'bytes':
|
||||||
return mix_in_length(merkleize(chunkify(coerce_to_bytes(value))), len(value))
|
return mix_in_length(merkleize(chunkify(coerce_to_bytes(value))), len(value))
|
||||||
|
# -------------------------------------
|
||||||
|
# merkleize([hash_tree_root(element) for element in value])
|
||||||
|
# if value is a vector of composite objects
|
||||||
|
elif isinstance(typ, list) and len(typ) == 2 and not is_basic(typ[0]):
|
||||||
|
return merkleize([hash_tree_root(element, typ[0]) for element in value])
|
||||||
|
# (needs some extra work for fixed-sized bytes array)
|
||||||
elif isinstance(typ, str) and typ[:5] == 'bytes' and len(typ) > 5:
|
elif isinstance(typ, str) and typ[:5] == 'bytes' and len(typ) > 5:
|
||||||
assert len(value) == int(typ[5:])
|
assert len(value) == int(typ[5:])
|
||||||
return merkleize(chunkify(coerce_to_bytes(value)))
|
return merkleize(chunkify(coerce_to_bytes(value)))
|
||||||
elif isinstance(typ, list) and len(typ) == 2 and not is_basic(typ[0]):
|
# or a container
|
||||||
return merkleize([hash_tree_root(element, typ[0]) for element in value])
|
|
||||||
elif hasattr(typ, 'fields'):
|
elif hasattr(typ, 'fields'):
|
||||||
return merkleize([hash_tree_root(getattr(value, field), subtype) for field, subtype in typ.fields.items()])
|
return merkleize([hash_tree_root(getattr(value, field), subtype) for field, subtype in typ.fields.items()])
|
||||||
|
# -------------------------------------
|
||||||
|
# mix_in_length(merkleize([hash_tree_root(element) for element in value]), len(value))
|
||||||
|
# if value is a list of composite objects
|
||||||
|
elif isinstance(typ, list) and len(typ) == 1 and not is_basic(typ[0]):
|
||||||
|
return mix_in_length(merkleize([hash_tree_root(element, typ[0]) for element in value]), len(value))
|
||||||
|
# -------------------------------------
|
||||||
else:
|
else:
|
||||||
raise Exception("Type not recognized")
|
raise Exception("Type not recognized")
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,8 @@ from eth2spec.phase0.spec import (
|
||||||
from tests.helpers import (
|
from tests.helpers import (
|
||||||
build_empty_block_for_next_slot,
|
build_empty_block_for_next_slot,
|
||||||
get_valid_attestation,
|
get_valid_attestation,
|
||||||
|
next_epoch,
|
||||||
|
next_slot,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,10 +122,12 @@ def test_non_zero_crosslink_data_root(state):
|
||||||
|
|
||||||
|
|
||||||
def test_bad_previous_crosslink(state):
|
def test_bad_previous_crosslink(state):
|
||||||
|
next_epoch(state)
|
||||||
attestation = get_valid_attestation(state)
|
attestation = get_valid_attestation(state)
|
||||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
for _ in range(spec.MIN_ATTESTATION_INCLUSION_DELAY):
|
||||||
|
next_slot(state)
|
||||||
|
|
||||||
state.latest_crosslinks[attestation.data.shard].epoch += 10
|
state.current_crosslinks[attestation.data.shard].epoch += 10
|
||||||
|
|
||||||
pre_state, post_state = run_attestation_processing(state, attestation, False)
|
pre_state, post_state = run_attestation_processing(state, attestation, False)
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ from tests.helpers import (
|
||||||
get_valid_proposer_slashing,
|
get_valid_proposer_slashing,
|
||||||
)
|
)
|
||||||
|
|
||||||
# mark entire file as 'header'
|
# mark entire file as 'proposer_slashings'
|
||||||
pytestmark = pytest.mark.proposer_slashings
|
pytestmark = pytest.mark.proposer_slashings
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ def overwrite_spec_config(config):
|
||||||
if field == "LATEST_RANDAO_MIXES_LENGTH":
|
if field == "LATEST_RANDAO_MIXES_LENGTH":
|
||||||
spec.BeaconState.fields['latest_randao_mixes'][1] = config[field]
|
spec.BeaconState.fields['latest_randao_mixes'][1] = config[field]
|
||||||
elif field == "SHARD_COUNT":
|
elif field == "SHARD_COUNT":
|
||||||
spec.BeaconState.fields['latest_crosslinks'][1] = config[field]
|
spec.BeaconState.fields['current_crosslinks'][1] = config[field]
|
||||||
|
spec.BeaconState.fields['previous_crosslinks'][1] = config[field]
|
||||||
elif field == "SLOTS_PER_HISTORICAL_ROOT":
|
elif field == "SLOTS_PER_HISTORICAL_ROOT":
|
||||||
spec.BeaconState.fields['latest_block_roots'][1] = config[field]
|
spec.BeaconState.fields['latest_block_roots'][1] = config[field]
|
||||||
spec.BeaconState.fields['latest_state_roots'][1] = config[field]
|
spec.BeaconState.fields['latest_state_roots'][1] = config[field]
|
||||||
|
|
|
@ -0,0 +1,136 @@
|
||||||
|
from copy import deepcopy
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import eth2spec.phase0.spec as spec
|
||||||
|
|
||||||
|
from eth2spec.phase0.state_transition import (
|
||||||
|
state_transition,
|
||||||
|
)
|
||||||
|
from eth2spec.phase0.spec import (
|
||||||
|
cache_state,
|
||||||
|
get_crosslink_deltas,
|
||||||
|
process_crosslinks,
|
||||||
|
)
|
||||||
|
from tests.helpers import (
|
||||||
|
add_attestation_to_state,
|
||||||
|
build_empty_block_for_next_slot,
|
||||||
|
fill_aggregate_attestation,
|
||||||
|
get_crosslink_committee_for_attestation,
|
||||||
|
get_valid_attestation,
|
||||||
|
next_epoch,
|
||||||
|
next_slot,
|
||||||
|
set_bitfield_bit,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# mark entire file as 'crosslinks'
|
||||||
|
pytestmark = pytest.mark.crosslinks
|
||||||
|
|
||||||
|
|
||||||
|
def run_process_crosslinks(state, valid=True):
|
||||||
|
# transition state to slot before state transition
|
||||||
|
slot = state.slot + (spec.SLOTS_PER_EPOCH - state.slot % spec.SLOTS_PER_EPOCH) - 1
|
||||||
|
block = build_empty_block_for_next_slot(state)
|
||||||
|
block.slot = slot
|
||||||
|
state_transition(state, block)
|
||||||
|
|
||||||
|
# cache state before epoch transition
|
||||||
|
cache_state(state)
|
||||||
|
|
||||||
|
post_state = deepcopy(state)
|
||||||
|
process_crosslinks(post_state)
|
||||||
|
|
||||||
|
return state, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_attestations(state):
|
||||||
|
pre_state, post_state = run_process_crosslinks(state)
|
||||||
|
|
||||||
|
for shard in range(spec.SHARD_COUNT):
|
||||||
|
assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]
|
||||||
|
|
||||||
|
return pre_state, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_crosslink_update_from_current_epoch(state):
|
||||||
|
next_epoch(state)
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(state)
|
||||||
|
|
||||||
|
fill_aggregate_attestation(state, attestation)
|
||||||
|
add_attestation_to_state(state, attestation, state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY)
|
||||||
|
|
||||||
|
assert len(state.current_epoch_attestations) == 1
|
||||||
|
|
||||||
|
pre_state, post_state = run_process_crosslinks(state)
|
||||||
|
|
||||||
|
shard = attestation.data.shard
|
||||||
|
assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
|
||||||
|
assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]
|
||||||
|
|
||||||
|
return pre_state, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_crosslink_update_from_previous_epoch(state):
|
||||||
|
next_epoch(state)
|
||||||
|
|
||||||
|
attestation = get_valid_attestation(state)
|
||||||
|
|
||||||
|
fill_aggregate_attestation(state, attestation)
|
||||||
|
add_attestation_to_state(state, attestation, state.slot + spec.SLOTS_PER_EPOCH)
|
||||||
|
|
||||||
|
assert len(state.previous_epoch_attestations) == 1
|
||||||
|
|
||||||
|
pre_state, post_state = run_process_crosslinks(state)
|
||||||
|
crosslink_deltas = get_crosslink_deltas(state)
|
||||||
|
|
||||||
|
shard = attestation.data.shard
|
||||||
|
assert post_state.previous_crosslinks[shard] != post_state.current_crosslinks[shard]
|
||||||
|
assert pre_state.current_crosslinks[shard] != post_state.current_crosslinks[shard]
|
||||||
|
# ensure rewarded
|
||||||
|
for index in get_crosslink_committee_for_attestation(state, attestation.data):
|
||||||
|
assert crosslink_deltas[0][index] > 0
|
||||||
|
assert crosslink_deltas[1][index] == 0
|
||||||
|
|
||||||
|
return pre_state, post_state
|
||||||
|
|
||||||
|
|
||||||
|
def test_double_late_crosslink(state):
|
||||||
|
next_epoch(state)
|
||||||
|
state.slot += 4
|
||||||
|
|
||||||
|
attestation_1 = get_valid_attestation(state)
|
||||||
|
fill_aggregate_attestation(state, attestation_1)
|
||||||
|
|
||||||
|
# add attestation_1 in the next epoch
|
||||||
|
next_epoch(state)
|
||||||
|
add_attestation_to_state(state, attestation_1, state.slot + 1)
|
||||||
|
|
||||||
|
for slot in range(spec.SLOTS_PER_EPOCH):
|
||||||
|
attestation_2 = get_valid_attestation(state)
|
||||||
|
if attestation_2.data.shard == attestation_1.data.shard:
|
||||||
|
break
|
||||||
|
next_slot(state)
|
||||||
|
fill_aggregate_attestation(state, attestation_2)
|
||||||
|
|
||||||
|
# add attestation_2 in the next epoch after attestation_1 has
|
||||||
|
# already updated the relevant crosslink
|
||||||
|
next_epoch(state)
|
||||||
|
add_attestation_to_state(state, attestation_2, state.slot + 1)
|
||||||
|
|
||||||
|
assert len(state.previous_epoch_attestations) == 1
|
||||||
|
assert len(state.current_epoch_attestations) == 0
|
||||||
|
|
||||||
|
pre_state, post_state = run_process_crosslinks(state)
|
||||||
|
crosslink_deltas = get_crosslink_deltas(state)
|
||||||
|
|
||||||
|
shard = attestation_2.data.shard
|
||||||
|
|
||||||
|
# ensure that the current crosslinks were not updated by the second attestation
|
||||||
|
assert post_state.previous_crosslinks[shard] == post_state.current_crosslinks[shard]
|
||||||
|
# ensure no reward, only penalties for the failed crosslink
|
||||||
|
for index in get_crosslink_committee_for_attestation(state, attestation_2.data):
|
||||||
|
assert crosslink_deltas[0][index] == 0
|
||||||
|
assert crosslink_deltas[1][index] > 0
|
||||||
|
|
||||||
|
return pre_state, post_state
|
|
@ -25,9 +25,9 @@ from eth2spec.phase0.spec import (
|
||||||
# functions
|
# functions
|
||||||
convert_to_indexed,
|
convert_to_indexed,
|
||||||
get_active_validator_indices,
|
get_active_validator_indices,
|
||||||
get_attestation_participants,
|
get_attesting_indices,
|
||||||
get_block_root,
|
get_block_root,
|
||||||
get_crosslink_committee_for_attestation,
|
get_crosslink_committees_at_slot,
|
||||||
get_current_epoch,
|
get_current_epoch,
|
||||||
get_domain,
|
get_domain,
|
||||||
get_empty_block,
|
get_empty_block,
|
||||||
|
@ -35,6 +35,7 @@ from eth2spec.phase0.spec import (
|
||||||
get_genesis_beacon_state,
|
get_genesis_beacon_state,
|
||||||
get_previous_epoch,
|
get_previous_epoch,
|
||||||
get_shard_delta,
|
get_shard_delta,
|
||||||
|
hash_tree_root,
|
||||||
slot_to_epoch,
|
slot_to_epoch,
|
||||||
verify_merkle_branch,
|
verify_merkle_branch,
|
||||||
hash,
|
hash,
|
||||||
|
@ -154,6 +155,7 @@ def build_attestation_data(state, slot, shard):
|
||||||
|
|
||||||
current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
|
current_epoch_start_slot = get_epoch_start_slot(get_current_epoch(state))
|
||||||
if slot < current_epoch_start_slot:
|
if slot < current_epoch_start_slot:
|
||||||
|
print(slot)
|
||||||
epoch_boundary_root = get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
epoch_boundary_root = get_block_root(state, get_epoch_start_slot(get_previous_epoch(state)))
|
||||||
elif slot == current_epoch_start_slot:
|
elif slot == current_epoch_start_slot:
|
||||||
epoch_boundary_root = block_root
|
epoch_boundary_root = block_root
|
||||||
|
@ -167,6 +169,7 @@ def build_attestation_data(state, slot, shard):
|
||||||
justified_epoch = state.current_justified_epoch
|
justified_epoch = state.current_justified_epoch
|
||||||
justified_block_root = state.current_justified_root
|
justified_block_root = state.current_justified_root
|
||||||
|
|
||||||
|
crosslinks = state.current_crosslinks if slot_to_epoch(slot) == get_current_epoch(state) else state.previous_crosslinks
|
||||||
return AttestationData(
|
return AttestationData(
|
||||||
slot=slot,
|
slot=slot,
|
||||||
shard=shard,
|
shard=shard,
|
||||||
|
@ -175,7 +178,7 @@ def build_attestation_data(state, slot, shard):
|
||||||
source_root=justified_block_root,
|
source_root=justified_block_root,
|
||||||
target_root=epoch_boundary_root,
|
target_root=epoch_boundary_root,
|
||||||
crosslink_data_root=spec.ZERO_HASH,
|
crosslink_data_root=spec.ZERO_HASH,
|
||||||
previous_crosslink=deepcopy(state.latest_crosslinks[shard]),
|
previous_crosslink_root=hash_tree_root(crosslinks[shard]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -273,6 +276,14 @@ def get_valid_attester_slashing(state):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_crosslink_committee_for_attestation(state, attestation_data):
|
||||||
|
"""
|
||||||
|
Return the crosslink committee corresponding to ``attestation_data``.
|
||||||
|
"""
|
||||||
|
crosslink_committees = get_crosslink_committees_at_slot(state, attestation_data.slot)
|
||||||
|
return [committee for committee, shard in crosslink_committees if shard == attestation_data.shard][0]
|
||||||
|
|
||||||
|
|
||||||
def get_valid_attestation(state, slot=None):
|
def get_valid_attestation(state, slot=None):
|
||||||
if slot is None:
|
if slot is None:
|
||||||
slot = state.slot
|
slot = state.slot
|
||||||
|
@ -297,7 +308,7 @@ def get_valid_attestation(state, slot=None):
|
||||||
custody_bitfield=custody_bitfield,
|
custody_bitfield=custody_bitfield,
|
||||||
aggregate_signature=EMPTY_SIGNATURE,
|
aggregate_signature=EMPTY_SIGNATURE,
|
||||||
)
|
)
|
||||||
participants = get_attestation_participants(
|
participants = get_attesting_indices(
|
||||||
state,
|
state,
|
||||||
attestation.data,
|
attestation.data,
|
||||||
attestation.aggregation_bitfield,
|
attestation.aggregation_bitfield,
|
||||||
|
@ -342,6 +353,13 @@ def fill_aggregate_attestation(state, attestation):
|
||||||
attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i)
|
attestation.aggregation_bitfield = set_bitfield_bit(attestation.aggregation_bitfield, i)
|
||||||
|
|
||||||
|
|
||||||
|
def add_attestation_to_state(state, attestation, slot):
|
||||||
|
block = build_empty_block_for_next_slot(state)
|
||||||
|
block.slot = slot
|
||||||
|
block.body.attestations.append(attestation)
|
||||||
|
state_transition(state, block)
|
||||||
|
|
||||||
|
|
||||||
def next_slot(state):
|
def next_slot(state):
|
||||||
block = build_empty_block_for_next_slot(state)
|
block = build_empty_block_for_next_slot(state)
|
||||||
state_transition(state, block)
|
state_transition(state, block)
|
||||||
|
|
|
@ -25,6 +25,7 @@ from eth2spec.phase0.spec import (
|
||||||
advance_slot,
|
advance_slot,
|
||||||
cache_state,
|
cache_state,
|
||||||
set_balance,
|
set_balance,
|
||||||
|
slot_to_epoch,
|
||||||
verify_merkle_branch,
|
verify_merkle_branch,
|
||||||
hash,
|
hash,
|
||||||
)
|
)
|
||||||
|
@ -382,33 +383,6 @@ def test_voluntary_exit(state):
|
||||||
return pre_state, [initiate_exit_block, exit_block], post_state
|
return pre_state, [initiate_exit_block, exit_block], post_state
|
||||||
|
|
||||||
|
|
||||||
def test_no_exit_churn_too_long_since_change(state):
|
|
||||||
pre_state = deepcopy(state)
|
|
||||||
validator_index = get_active_validator_indices(
|
|
||||||
pre_state,
|
|
||||||
get_current_epoch(pre_state)
|
|
||||||
)[-1]
|
|
||||||
|
|
||||||
#
|
|
||||||
# setup pre_state
|
|
||||||
#
|
|
||||||
# move state forward PERSISTENT_COMMITTEE_PERIOD epochs to allow for exit
|
|
||||||
pre_state.slot += spec.PERSISTENT_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
|
|
||||||
|
|
||||||
post_state = deepcopy(pre_state)
|
|
||||||
|
|
||||||
#
|
|
||||||
# Process registry change but ensure no exit
|
|
||||||
#
|
|
||||||
block = build_empty_block_for_next_slot(post_state)
|
|
||||||
block.slot += spec.SLOTS_PER_EPOCH
|
|
||||||
state_transition(post_state, block)
|
|
||||||
|
|
||||||
assert post_state.validator_registry[validator_index].exit_epoch == spec.FAR_FUTURE_EPOCH
|
|
||||||
|
|
||||||
return pre_state, [block], post_state
|
|
||||||
|
|
||||||
|
|
||||||
def test_transfer(state):
|
def test_transfer(state):
|
||||||
pre_state = deepcopy(state)
|
pre_state = deepcopy(state)
|
||||||
current_epoch = get_current_epoch(pre_state)
|
current_epoch = get_current_epoch(pre_state)
|
||||||
|
|
Loading…
Reference in New Issue