mirror of
https://github.com/status-im/eth2.0-specs.git
synced 2025-02-01 05:14:49 +00:00
Revamp 1_shard-data-chains.md
WIP! * Significant simplifications * A few bug fixes * Lots of cleanups and reorganising (making it consistent with `0_beacon-chain.md`) * Likely a few bugs introduced
This commit is contained in:
parent
2abb81d4e9
commit
334d6c6bc7
@ -17,31 +17,37 @@
|
||||
- [State list lengths](#state-list-lengths)
|
||||
- [Rewards and penalties](#rewards-and-penalties)
|
||||
- [Signature domain types](#signature-domain-types)
|
||||
- [TODO PLACEHOLDER](#todo-placeholder)
|
||||
- [Data structures](#data-structures)
|
||||
- [`ShardBlockHeader`](#shardblockheader)
|
||||
- [`ShardBlock`](#shardblock)
|
||||
- [Containers](#containers)
|
||||
- [`ShardBlockSignatures`](#shardblocksignatures)
|
||||
- [`ShardBlockCore`](#shardblockcore)
|
||||
- [`ExtendedShardBlockCore`](#extendedshardblockcore)
|
||||
- [`ShardBlock`](#shardblock)
|
||||
- [`ShardBlockHeader`](#shardblockheader)
|
||||
- [`ShardState`](#shardstate)
|
||||
- [`ShardReceiptDelta`](#shardreceiptdelta)
|
||||
- [Helper functions](#helper-functions)
|
||||
- [`compute_slot_of_shard_slot`](#compute_slot_of_shard_slot)
|
||||
- [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot)
|
||||
- [`get_shard_period_start_epoch`](#get_shard_period_start_epoch)
|
||||
- [`get_period_committee`](#get_period_committee)
|
||||
- [`get_persistent_committee`](#get_persistent_committee)
|
||||
- [`get_shard_block_proposer_index`](#get_shard_block_proposer_index)
|
||||
- [`get_shard_header`](#get_shard_header)
|
||||
- [`pad`](#pad)
|
||||
- [`flatten_shard_header`](#flatten_shard_header)
|
||||
- [`compute_crosslink_data_root`](#compute_crosslink_data_root)
|
||||
- [`get_default_shard_state`](#get_default_shard_state)
|
||||
- [Misc](#misc-1)
|
||||
- [`pad`](#pad)
|
||||
- [`compute_slot_of_shard_slot`](#compute_slot_of_shard_slot)
|
||||
- [`compute_epoch_of_shard_slot`](#compute_epoch_of_shard_slot)
|
||||
- [`compute_period_start_epoch`](#compute_period_start_epoch)
|
||||
- [`compute_flat_shard_header`](#compute_flat_shard_header)
|
||||
- [`compute_crosslink_data_root`](#compute_crosslink_data_root)
|
||||
- [State accessors](#state-accessors)
|
||||
- [`get_period_committee`](#get_period_committee)
|
||||
- [`get_persistent_committee`](#get_persistent_committee)
|
||||
- [`get_shard_proposer_index`](#get_shard_proposer_index)
|
||||
- [`get_default_shard_state`](#get_default_shard_state)
|
||||
- [`get_shard_base_reward`](#get_shard_base_reward)
|
||||
- [State mutators](#state-mutators)
|
||||
- [`add_fee`](#add_fee)
|
||||
- [`add_reward`](#add_reward)
|
||||
- [Shard state transition function](#shard-state-transition-function)
|
||||
- [Period processing](#period-processing)
|
||||
- [Block processing](#block-processing)
|
||||
- [Block header](#block-header)
|
||||
- [Attestations](#attestations)
|
||||
- [Block data fees](#block-data-fees)
|
||||
- [Object validity](#object-validity)
|
||||
- [Shard block validation: preliminary](#shard-block-validation-preliminary)
|
||||
- [Shard state transition function helpers](#shard-state-transition-function-helpers)
|
||||
- [Shard state transition function](#shard-state-transition-function)
|
||||
- [Beacon attestations](#beacon-attestations)
|
||||
- [Shard fork choice rule](#shard-fork-choice-rule)
|
||||
|
||||
@ -53,11 +59,9 @@ This document describes the shard data layer and the shard fork choice rule in P
|
||||
|
||||
## Custom types
|
||||
|
||||
We define the following Python custom types for type hinting and readability:
|
||||
|
||||
| Name | SSZ equivalent | Description |
|
||||
| - | - | - |
|
||||
| `ShardSlot` | `uint64` | a slot number in shard chain |
|
||||
| `ShardSlot` | `uint64` | a shard slot number |
|
||||
|
||||
## Configuration
|
||||
|
||||
@ -65,7 +69,7 @@ We define the following Python custom types for type hinting and readability:
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `SHARD_SLOTS_PER_BEACON_SLOT` | `2**1` (= 2) |
|
||||
| `SHARD_SLOTS_PER_EPOCH` | `2**7` (= 128) |
|
||||
| `TARGET_PERSISTENT_COMMITTEE_SIZE` | `2**7` (= 128) |
|
||||
| `SHARD_HEADER_SIZE` | `2**9` (= 512) |
|
||||
| `SHARD_BLOCK_SIZE_TARGET` | `2**14` (= 16,384) |
|
||||
@ -76,7 +80,6 @@ We define the following Python custom types for type hinting and readability:
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `PHASE_1_FORK_EPOCH` | **TBD** |
|
||||
| `PHASE_1_FORK_SLOT` | **TBD** |
|
||||
|
||||
### Time parameters
|
||||
|
||||
@ -96,75 +99,53 @@ We define the following Python custom types for type hinting and readability:
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `BASEFEE_ADJUSTMENT_FACTOR` | `2**3` (= 8) |
|
||||
| `REWARD_COEFFICIENT_BASE` | `2**20` ( = 1,048,576) |
|
||||
| `REWARD_COEFFICIENT_BASE` | `2**20` (= 1,048,576) |
|
||||
|
||||
### Signature domain types
|
||||
|
||||
The following types are defined, mapping into `DomainType` (little endian):
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `DOMAIN_SHARD_PROPOSER` | `128` |
|
||||
| `DOMAIN_SHARD_ATTESTER` | `129` |
|
||||
|
||||
### TODO PLACEHOLDER
|
||||
## Containers
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `PLACEHOLDER` | `2**3` |
|
||||
|
||||
## Data structures
|
||||
|
||||
_Note: the shard block header structure is carefully designed so that all of the values have the same depth in a hash tree implementation, so `hash_tree_root(SSZ_partial(x)) == hash_tree_root(x)` (using the "left-to-right leaves" scheme [here](https://github.com/ethereum/eth2.0-specs/issues/1303)), which allows shard block headers to look like an SSZ object when in the crosslink structure. This is done by balancing it so that 7 or 8 items are on the left side (the "core") and two 96-byte (ie. 3*2 = 6 chunk) items are on the right side. Change with care._
|
||||
|
||||
### `ShardBlockHeader`
|
||||
### `ShardBlockSignatures`
|
||||
|
||||
```python
|
||||
class ShardBlockHeader(Container):
|
||||
core: ShardBlockCore
|
||||
signatures: ShardBlockSignatures
|
||||
class ShardBlockSignatures(Container):
|
||||
attesters: BLSSignature
|
||||
proposer: BLSSignature
|
||||
```
|
||||
|
||||
### `ShardBlock`
|
||||
|
||||
```python
|
||||
class ShardBlock(Container):
|
||||
core: ExtendedShardBlockCore
|
||||
slot: ShardSlot
|
||||
beacon_chain_root: Hash
|
||||
parent_root: Hash
|
||||
state_root: Hash
|
||||
aggregation_bits: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2]
|
||||
total_bytes: uint64
|
||||
body: Bytes[SHARD_BLOCK_SIZE_LIMIT - SHARD_HEADER_SIZE]
|
||||
padding: Bytes[32]
|
||||
signatures: ShardBlockSignatures
|
||||
```
|
||||
|
||||
### `ShardBlockSignatures`
|
||||
### `ShardBlockHeader`
|
||||
|
||||
```python
|
||||
class ShardBlockSignatures(Container):
|
||||
attestation_signature: BLSSignature
|
||||
proposer_signature: BLSSignature
|
||||
```
|
||||
|
||||
### `ShardBlockCore`
|
||||
|
||||
```python
|
||||
class ShardBlockCore(Container):
|
||||
class ShardBlockHeader(Container):
|
||||
slot: ShardSlot
|
||||
beacon_chain_root: Hash
|
||||
parent_root: Hash
|
||||
data_root: Hash
|
||||
state_root: Hash
|
||||
aggregation_bits: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2]
|
||||
total_bytes: uint64
|
||||
attester_bitfield: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2]
|
||||
```
|
||||
|
||||
### `ExtendedShardBlockCore`
|
||||
|
||||
```python
|
||||
class ExtendedShardBlockCore(Container):
|
||||
slot: ShardSlot
|
||||
beacon_chain_root: Hash
|
||||
parent_root: Hash
|
||||
data: Bytes[SHARD_BLOCK_SIZE_LIMIT - SHARD_HEADER_SIZE]
|
||||
state_root: Hash
|
||||
total_bytes: uint64
|
||||
attester_bitfield: Bitvector[TARGET_PERSISTENT_COMMITTEE_SIZE * 2]
|
||||
body_root: Hash
|
||||
padding: Bytes[32]
|
||||
signatures: ShardBlockSignatures
|
||||
```
|
||||
|
||||
### `ShardState`
|
||||
@ -179,7 +160,7 @@ class ShardState(Container):
|
||||
basefee: Gwei
|
||||
slot: ShardSlot
|
||||
shard: Shard
|
||||
most_recent_block_core: ShardBlockCore
|
||||
latest_block_header: ShardBlockHeader
|
||||
receipt_root: Hash
|
||||
total_bytes: uint64
|
||||
```
|
||||
@ -195,34 +176,67 @@ class ShardReceiptDelta(Container):
|
||||
|
||||
## Helper functions
|
||||
|
||||
### `compute_slot_of_shard_slot`
|
||||
### Misc
|
||||
|
||||
#### `pad`
|
||||
|
||||
```python
|
||||
def compute_slot_of_shard_slot(slot: ShardSlot) -> Epoch:
|
||||
return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT)
|
||||
def pad(x: bytes, length: uint64) -> bytes:
|
||||
assert len(x) <= length
|
||||
return x + b'\x00' * (length - len(x))
|
||||
```
|
||||
|
||||
### `compute_epoch_of_shard_slot`
|
||||
#### `compute_epoch_of_shard_slot`
|
||||
|
||||
```python
|
||||
def compute_epoch_of_shard_slot(slot: ShardSlot) -> Epoch:
|
||||
return Epoch(slot // SHARD_SLOTS_PER_BEACON_SLOT // SLOTS_PER_EPOCH)
|
||||
return compute_epoch_of_slot(compute_slot_of_shard_slot(slot))
|
||||
```
|
||||
|
||||
### `get_shard_period_start_epoch`
|
||||
#### `compute_period_start_epoch`
|
||||
|
||||
```python
|
||||
def get_shard_period_start_epoch(epoch: Epoch, lookback: int=0) -> Epoch:
|
||||
def compute_period_start_epoch(epoch: Epoch, lookback: Epoch=0) -> Epoch:
|
||||
return Epoch(epoch - (epoch % EPOCHS_PER_SHARD_PERIOD) - lookback * EPOCHS_PER_SHARD_PERIOD)
|
||||
```
|
||||
|
||||
### `get_period_committee`
|
||||
#### `compute_flat_shard_header`
|
||||
|
||||
```python
|
||||
def compute_flat_shard_header(block: ShardBlock) -> Bytes[SHARD_HEADER_SIZE]:
|
||||
"""
|
||||
Return a flat serialisation of the ``block`` header which preserves hash tree root.
|
||||
"""
|
||||
return (
|
||||
pad(int_to_bytes(block.slot, length=8), 32) +
|
||||
block.beacon_chain_root +
|
||||
block.parent_root +
|
||||
hash_tree_root(block.body) +
|
||||
block.state_root +
|
||||
pad(int_to_bytes(block.total_bytes, length=8), 32) +
|
||||
bytes([sum([block.aggregation_bits[i + j] << j for j in range(8)]) for i in range(0, 256, 8)]) +
|
||||
block.padding +
|
||||
pad(block.signatures.attesters, 128) +
|
||||
pad(block.signatures.proposer, 128)
|
||||
)
|
||||
```
|
||||
|
||||
#### `compute_crosslink_data_root`
|
||||
|
||||
```python
|
||||
def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash:
|
||||
headers = b''.join([compute_flat_shard_header(block) for block in blocks])
|
||||
bodies = b''.join([block.body for block in blocks])
|
||||
MAX_SIZE = SHARD_BLOCK_SIZE_LIMIT * SHARD_SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK
|
||||
return hash_tree_root(BytesN[MAX_SIZE](pad(headers + bodies, MAX_SIZE)))
|
||||
```
|
||||
|
||||
### State accessors
|
||||
|
||||
#### `get_period_committee`
|
||||
|
||||
```python
|
||||
def get_period_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]:
|
||||
"""
|
||||
Return committee for a period. Used to construct persistent committees.
|
||||
"""
|
||||
full_committee = compute_committee(
|
||||
indices=get_active_validator_indices(state, epoch),
|
||||
seed=get_seed(state, epoch),
|
||||
@ -233,19 +247,12 @@ def get_period_committee(state: BeaconState, epoch: Epoch, shard: Shard) -> Sequ
|
||||
return full_committee[:TARGET_PERSISTENT_COMMITTEE_SIZE]
|
||||
```
|
||||
|
||||
### `get_persistent_committee`
|
||||
#### `get_persistent_committee`
|
||||
|
||||
```python
|
||||
def get_persistent_committee(state: BeaconState,
|
||||
shard: Shard,
|
||||
slot: ShardSlot) -> Sequence[ValidatorIndex]:
|
||||
"""
|
||||
Return the persistent committee for the given ``shard`` at the given ``slot``.
|
||||
"""
|
||||
epoch = compute_epoch_of_shard_slot(slot)
|
||||
|
||||
earlier_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=2), shard)
|
||||
later_committee = get_period_committee(state, get_shard_period_start_epoch(epoch, lookback=1), shard)
|
||||
def get_persistent_committee(state: BeaconState, shard: Shard, epoch: Epoch) -> Sequence[ValidatorIndex]:
|
||||
earlier_committee = get_period_committee(state, compute_period_start_epoch(epoch, lookback=2), shard)
|
||||
later_committee = get_period_committee(state, compute_period_start_epoch(epoch, lookback=1), shard)
|
||||
|
||||
# Take not-yet-cycled-out validators from earlier committee and already-cycled-in validators from
|
||||
# later committee; return a sorted list of the union of the two, deduplicated
|
||||
@ -255,22 +262,17 @@ def get_persistent_committee(state: BeaconState,
|
||||
))
|
||||
```
|
||||
|
||||
### `get_shard_block_proposer_index`
|
||||
#### `get_shard_proposer_index`
|
||||
|
||||
```python
|
||||
def get_shard_block_proposer_index(state: BeaconState,
|
||||
shard: Shard,
|
||||
slot: ShardSlot) -> Optional[ValidatorIndex]:
|
||||
# Randomly shift persistent committee
|
||||
persistent_committee = list(get_persistent_committee(state, shard, slot))
|
||||
current_epoch = get_current_epoch(state)
|
||||
|
||||
active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], current_epoch)]
|
||||
if not any(active_indices):
|
||||
return None
|
||||
def get_shard_proposer_index(state: BeaconState, shard: Shard, slot: ShardSlot) -> ValidatorIndex:
|
||||
epoch = get_current_epoch(state)
|
||||
persistent_committee = list(get_persistent_committee(state, shard, epoch))
|
||||
active_indices = [i for i in persistent_committee if is_active_validator(state.validators[i], epoch)]
|
||||
assert len(active_indices) > 0
|
||||
|
||||
MAX_RANDOM_BYTE = 2**8 - 1
|
||||
seed = hash(get_seed(state, current_epoch) + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8))
|
||||
seed = hash(get_seed(state, epoch) + int_to_bytes(shard, length=8) + int_to_bytes(slot, length=8))
|
||||
i = 0
|
||||
while True:
|
||||
candidate_index = active_indices[(slot + i) % len(active_indices)]
|
||||
@ -281,68 +283,7 @@ def get_shard_block_proposer_index(state: BeaconState,
|
||||
i += 1
|
||||
```
|
||||
|
||||
### `get_shard_header`
|
||||
|
||||
```python
|
||||
def get_shard_header(block: ShardBlock) -> ShardBlockHeader:
|
||||
return ShardBlockHeader(
|
||||
core=ShardBlockCore(
|
||||
slot=block.core.slot,
|
||||
beacon_chain_root=block.core.beacon_chain_root,
|
||||
parent_root=block.core.parent_root,
|
||||
data_root=hash_tree_root(block.core.data),
|
||||
state_root=block.core.state_root,
|
||||
total_bytes=block.core.total_bytes,
|
||||
attester_bitfield=block.core.attester_bitfield,
|
||||
),
|
||||
signatures=block.signatures,
|
||||
)
|
||||
```
|
||||
|
||||
### `pad`
|
||||
|
||||
```python
|
||||
def pad(x: bytes, length: int) -> bytes:
|
||||
assert len(x) <= length
|
||||
return x + b'\x00' * (length - len(x))
|
||||
```
|
||||
|
||||
### `flatten_shard_header`
|
||||
|
||||
```python
|
||||
def flatten_shard_header(header: ShardBlockHeader) -> Bytes[SHARD_HEADER_SIZE]:
|
||||
"""
|
||||
Converts a shard block header into a flat object with the same hash tree root. Used
|
||||
in the crosslink construction.
|
||||
"""
|
||||
committee_size = len(header.core.attester_bitfield)
|
||||
attester_bits = [header.core.attester_bitfield[i] if i < committee_size else 0 for i in range(256)]
|
||||
attester_bytes = bytes([sum([attester_bits[i + j] << j for j in range(8)]) for i in range(0, 256, 8)])
|
||||
return (
|
||||
pad(int_to_bytes(header.core.slot, length=8), 32) +
|
||||
header.core.beacon_chain_root +
|
||||
header.core.parent_root +
|
||||
header.core.data_root +
|
||||
header.core.state_root +
|
||||
pad(int_to_bytes(header.core.total_bytes, length=8), 32) +
|
||||
attester_bytes +
|
||||
b'\x00' * 32 +
|
||||
pad(header.signatures.attestation_signature, 128) +
|
||||
pad(header.signatures.proposer_signature, 128)
|
||||
)
|
||||
```
|
||||
|
||||
### `compute_crosslink_data_root`
|
||||
|
||||
```python
|
||||
def compute_crosslink_data_root(blocks: Sequence[ShardBlock]) -> Hash:
|
||||
header = b''.join([flatten_shard_header(get_shard_header(block)) for block in blocks])
|
||||
footer = b''.join([block.core.data for block in blocks])
|
||||
MAX_SIZE = SHARD_BLOCK_SIZE_LIMIT * SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * MAX_EPOCHS_PER_CROSSLINK
|
||||
return hash_tree_root(BytesN[MAX_SIZE](pad(header + footer, MAX_SIZE)))
|
||||
```
|
||||
|
||||
### `get_default_shard_state`
|
||||
#### `get_default_shard_state`
|
||||
|
||||
```python
|
||||
def get_default_shard_state(beacon_state: BeaconState, shard: Shard) -> ShardState:
|
||||
@ -359,7 +300,7 @@ def get_default_shard_state(beacon_state: BeaconState, shard: Shard) -> ShardSta
|
||||
return ShardState(
|
||||
basefee=1,
|
||||
shard=shard,
|
||||
slot=PHASE_1_FORK_SLOT,
|
||||
slot=ShardSlot(PHASE_1_FORK_EPOCH * SHARD_SLOTS_PER_EPOCH),
|
||||
earlier_committee_rewards=[REWARD_COEFFICIENT_BASE for _ in range(len(earlier_committee))],
|
||||
later_committee_rewards=[REWARD_COEFFICIENT_BASE for _ in range(len(later_committee))],
|
||||
earlier_committee_fees=[Gwei(0) for _ in range(len(earlier_committee))],
|
||||
@ -367,195 +308,206 @@ def get_default_shard_state(beacon_state: BeaconState, shard: Shard) -> ShardSta
|
||||
)
|
||||
```
|
||||
|
||||
#### `get_shard_base_reward`
|
||||
|
||||
```python
|
||||
def get_shard_base_reward(beacon_state: BeaconState) -> Gwei:
|
||||
total_balance_root = integer_squareroot(get_total_active_balance(beacon_state))
|
||||
return Gwei(REWARD_COEFFICIENT_BASE * BASE_REWARD_FACTOR // total_balance_root // BASE_REWARDS_PER_EPOCH)
|
||||
```
|
||||
|
||||
### State mutators
|
||||
|
||||
#### `add_reward`
|
||||
|
||||
```python
|
||||
def add_reward(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None:
|
||||
epoch = compute_epoch_of_shard_slot(state.slot)
|
||||
earlier_committee = get_period_committee(beacon_state, compute_period_start_epoch(epoch, lookback=2), state.shard)
|
||||
later_committee = get_period_committee(beacon_state, compute_period_start_epoch(epoch, lookback=1), state.shard)
|
||||
if index in earlier_committee:
|
||||
state.earlier_committee_rewards[earlier_committee.index(index)] += delta
|
||||
elif index in later_committee:
|
||||
state.later_committee_rewards[later_committee.index(index)] += delta
|
||||
```
|
||||
|
||||
#### `add_fee`
|
||||
|
||||
```python
|
||||
def add_fee(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: Gwei) -> None:
|
||||
epoch = compute_epoch_of_shard_slot(state.slot)
|
||||
earlier_committee = get_period_committee(beacon_state, compute_period_start_epoch(epoch, lookback=2), state.shard)
|
||||
later_committee = get_period_committee(beacon_state, compute_period_start_epoch(epoch, lookback=1), state.shard)
|
||||
if index in earlier_committee:
|
||||
state.earlier_committee_fees[earlier_committee.index(index)] += delta
|
||||
elif index in later_committee:
|
||||
state.later_committee_fees[later_committee.index(index)] += delta
|
||||
```
|
||||
|
||||
## Shard state transition function
|
||||
|
||||
The post-state corresponding to a pre-state `state`, a beacon state `beacon_state`, and a block `block` is defined as `shard_state_transition(state, beacon_state, block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid.
|
||||
|
||||
```python
|
||||
def shard_state_transition(state: ShardState,
|
||||
beacon_state: BeaconState,
|
||||
block: ShardBlock,
|
||||
validate_state_root: bool=False) -> ShardState:
|
||||
# Process slots (including those with no blocks) since block
|
||||
process_shard_slots(state, beacon_state, block.slot)
|
||||
# Process block
|
||||
process_shard_block(state, beacon_state, block)
|
||||
# Validate state root (`validate_state_root == True` in production)
|
||||
if validate_state_root:
|
||||
assert block.state_root == hash_tree_root(state)
|
||||
# Return post-state
|
||||
return state
|
||||
```
|
||||
|
||||
```python
|
||||
def process_shard_slots(state: ShardState, beacon_state: BeaconState, slot: ShardSlot) -> None:
|
||||
assert state.slot <= slot
|
||||
while state.slot < slot:
|
||||
process_shard_slot(state)
|
||||
# Process period on the start slot of the next period
|
||||
if (state.slot + 1) % (SHARD_SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD) == 0:
|
||||
process_shard_period(state)
|
||||
state.slot += ShardSlot(1)
|
||||
```
|
||||
|
||||
```python
|
||||
def process_shard_slot(state: ShardState, beacon_state: BeaconState, slot: ShardSlot) -> None:
|
||||
# Cache state root
|
||||
if state.latest_block_header.state_root == Hash():
|
||||
state.latest_block_header.state_root = hash_tree_root(state)
|
||||
# Save state roots in history accumulator
|
||||
depth = 0
|
||||
state_root = hash_tree_root(state)
|
||||
while state.slot % 2**depth == 0 and depth < HISTORY_ACCUMULATOR_VECTOR:
|
||||
state.history_accumulator[depth] = state_root
|
||||
depth += 1
|
||||
```
|
||||
|
||||
### Period processing
|
||||
|
||||
```python
|
||||
def process_shard_period(state: ShardState, beacon_state: BeaconState) -> None:
|
||||
epoch = compute_epoch_of_shard_slot(state.slot)
|
||||
earlier_committee = get_period_committee(
|
||||
beacon_state,
|
||||
compute_period_start_epoch(epoch, lookback=2),
|
||||
state.shard,
|
||||
)
|
||||
later_committee = get_period_committee(
|
||||
beacon_state,
|
||||
compute_period_start_epoch(epoch, lookback=1),
|
||||
state.shard,
|
||||
)
|
||||
|
||||
state.receipt_root = hash_tree_root(List[ShardReceiptDelta, TARGET_PERSISTENT_COMMITTEE_SIZE]([
|
||||
ShardReceiptDelta(validator_index, state.earlier_committee_rewards[i], state.earlier_committee_fees[i])
|
||||
for i, validator_index in enumerate(earlier_committee)
|
||||
]))
|
||||
state.earlier_committee_rewards = state.later_committee_rewards
|
||||
state.earlier_committee_fees = state.later_committee_fees
|
||||
state.later_committee_rewards = [REWARD_COEFFICIENT_BASE for _ in range(len(later_committee))]
|
||||
state.later_committee_fees = [Gwei(0) for _ in range(len(later_committee))]
|
||||
```
|
||||
|
||||
### Block processing
|
||||
|
||||
```python
|
||||
def process_shard_block(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None:
|
||||
process_shard_block_header(state, beacon_state, block)
|
||||
process_shard_attestations(state, beacon_state, block
|
||||
process_shard_block_data_fees(state, beacon_state, block)
|
||||
```
|
||||
|
||||
#### Block header
|
||||
|
||||
```python
|
||||
def process_shard_block_header(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None:
|
||||
# Verify that the slots match
|
||||
assert block.slot == state.slot
|
||||
# Verify that the parent matches
|
||||
if block.parent_root != Hash():
|
||||
assert block.parent_root == signing_root(state.latest_block_header)
|
||||
# Save current block as the new latest block
|
||||
state.latest_block_header = ShardBlockHeader(
|
||||
slot=block.slot,
|
||||
beacon_chain_root=block.beacon_chain_root,
|
||||
parent_root=block.parent_root,
|
||||
# `state_root` is zeroed and overwritten in the next `process_shard_slot` call
|
||||
aggregation_bits=block.aggregation_bits,
|
||||
total_bytes=block.total_bytes,
|
||||
body_root=hash_tree_root(block.body),
|
||||
# `signatures` is zeroed
|
||||
)
|
||||
# Verify proposer signature
|
||||
proposer_index = get_shard_proposer_index(beacon_state, state.shard, block.slot)
|
||||
pubkey = beacon_state.validators[proposer_index].pubkey
|
||||
domain = get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.slot))
|
||||
assert bls_verify(pubkey, signing_root(block), block.signatures.proposer, domain)
|
||||
# Verify total bytes count
|
||||
state.total_bytes += len(block.body)
|
||||
assert block.total_bytes == state.total_bytes
|
||||
```
|
||||
|
||||
#### Attestations
|
||||
|
||||
```python
|
||||
def process_shard_attestations(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None:
|
||||
persistent_committee = get_persistent_committee(beacon_state, state.shard, block.slot)
|
||||
pubkeys = []
|
||||
attestation_count = 0
|
||||
base_reward = get_shard_base_reward(beacon_state)
|
||||
for i, validator_index in enumerate(persistent_committee):
|
||||
if block.aggregation_bits[i]:
|
||||
pubkeys.append(beacon_state.validators[validator_index].pubkey)
|
||||
add_reward(state, beacon_state, validator_index, base_reward)
|
||||
attestation_count += 1
|
||||
for i in range(len(persistent_committee), TARGET_PERSISTENT_COMMITTEE_SIZE):
|
||||
assert block.aggregation_bits[i] is False or block.aggregation_bits[i] == 0 # TODO: Fix Bitvector
|
||||
# Verify aggregate signature
|
||||
domain = get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.slot))
|
||||
assert bls_verify(bls_aggregate_pubkeys(pubkeys), block.parent_root, block.signatures.attesters, domain)
|
||||
# Proposer micro-rewards
|
||||
add_reward(state, beacon_state, proposer_index, attestation_count * get_shard_base_reward(beacon_state) // PROPOSER_REWARD_QUOTIENT)
|
||||
```
|
||||
|
||||
#### Block data fees
|
||||
|
||||
```python
|
||||
def process_shard_block_data_fees(state: ShardState, beacon_state: BeaconState, block: ShardBlock) -> None:
|
||||
base_reward = get_shard_base_reward(beacon_state)
|
||||
add_fee(state, beacon_state, proposer_index, state.basefee * len(block.body) // SHARD_BLOCK_SIZE_LIMIT)
|
||||
QUOTIENT = SHARD_BLOCK_SIZE_LIMIT * BASEFEE_ADJUSTMENT_FACTOR
|
||||
if len(block.body) > SHARD_BLOCK_SIZE_TARGET:
|
||||
state.basefee += Gwei(max(1, state.basefee * (len(block.body) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT))
|
||||
elif len(block.body) < SHARD_BLOCK_SIZE_TARGET:
|
||||
state.basefee -= Gwei(max(1, state.basefee * (len(block.body) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT))
|
||||
state.basefee = Gwei(max(1, min( EFFECTIVE_BALANCE_INCREMENT // EPOCHS_PER_SHARD_PERIOD // SHARD_SLOTS_PER_EPOCH,
|
||||
state.basefee,
|
||||
)))
|
||||
```
|
||||
|
||||
## Object validity
|
||||
|
||||
### Shard block validation: preliminary
|
||||
|
||||
Accept a shard block `block` only if all of the following are correct:
|
||||
|
||||
* Either `block.core.parent_root == Hash()` or a block `parent` such that `hash_tree_root(parent.core) == block.core.parent_root` has already been accepted.
|
||||
* `block.core.beacon_chain_root == get_block_root(head_beacon_state, compute_epoch_of_shard_slot(parent.core.slot))` where `head_beacon_state` is the current beacon chain head state. Alternatively phrased, a beacon chain block `beacon_ref` such that `signing_root(beacon_ref) == block.core.beacon_chain_root` has already been accepted and is part of the canonical chain, and no block with slot `beacon_ref.slot < slot <= compute_start_slot_of_epoch(compute_epoch_of_shard_slot(parent.core.slot))` is part of the canonical chain.
|
||||
* Let `beacon_state` be the state where `beacon_ref.state_root == hash_tree_root(beacon_state)`. Let `prev_state` be the post-state of the `parent` if the `parent` exists, otherwise let it be `get_default_shard_state(beacon_state, shard)` (defined below). `block.core.state_root` must equal the `hash_tree_root` of the state after applying `shard_state_transition(prev_state, beacon_state, block)`.
|
||||
* Either `block.parent_root == Hash()` or a block `parent` such that `signing_root(parent) == block.parent_root` has already been accepted.
|
||||
* `block.beacon_chain_root == get_block_root(head_beacon_state, compute_epoch_of_shard_slot(parent.slot))` where `head_beacon_state` is the current beacon chain head state. Alternatively phrased, a beacon chain block `beacon_ref` such that `signing_root(beacon_ref) == block.beacon_chain_root` has already been accepted and is part of the canonical chain, and no block with slot `beacon_ref.slot < slot <= compute_start_slot_of_epoch(compute_epoch_of_shard_slot(parent.slot))` is part of the canonical chain.
|
||||
* Let `beacon_state` be the state where `beacon_ref.state_root == hash_tree_root(beacon_state)`. Let `prev_state` be the post-state of the `parent` if the `parent` exists, otherwise let it be `get_default_shard_state(beacon_state, shard)` (defined below). `block.state_root` must equal the `hash_tree_root` of the state after applying `shard_state_transition(prev_state, beacon_state, block)`.
|
||||
|
||||
Note that these acceptance conditions depend on the canonical beacon chain; when the canonical beacon chain reorganizes, the eligibility of shard blocks should be re-evaluated.
|
||||
|
||||
### Shard state transition function helpers
|
||||
|
||||
```python
|
||||
def add_reward(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: int) -> None:
|
||||
epoch = compute_epoch_of_shard_slot(state.slot)
|
||||
earlier_committee = get_period_committee(
|
||||
beacon_state,
|
||||
get_shard_period_start_epoch(epoch, lookback=2),
|
||||
state.shard,
|
||||
)
|
||||
later_committee = get_period_committee(beacon_state, get_shard_period_start_epoch(epoch, lookback=1), state.shard)
|
||||
if index in earlier_committee:
|
||||
state.earlier_committee_rewards[earlier_committee.index(index)] += delta
|
||||
elif index in later_committee:
|
||||
state.later_committee_rewards[later_committee.index(index)] += delta
|
||||
else:
|
||||
raise Exception("Should never be here")
|
||||
```
|
||||
|
||||
```python
|
||||
def add_fee(state: ShardState, beacon_state: BeaconState, index: ValidatorIndex, delta: int) -> None:
|
||||
epoch = compute_epoch_of_shard_slot(state.slot)
|
||||
earlier_committee = get_period_committee(beacon_state, get_shard_period_start_epoch(epoch, lookback=2), state.shard)
|
||||
later_committee = get_period_committee(beacon_state, get_shard_period_start_epoch(epoch, lookback=1), state.shard)
|
||||
if index in earlier_committee:
|
||||
state.earlier_committee_fees[earlier_committee.index(index)] += delta
|
||||
elif index in later_committee:
|
||||
state.later_committee_fees[later_committee.index(index)] += delta
|
||||
else:
|
||||
raise Exception("Should never be here")
|
||||
```
|
||||
|
||||
### Shard state transition function
|
||||
|
||||
```python
|
||||
def shard_state_transition(state: ShardState,
|
||||
beacon_state: BeaconState,
|
||||
block: ShardBlock,
|
||||
validate_state_root: bool=False) -> None:
|
||||
assert block.core.slot > state.slot
|
||||
for slot in range(state.slot, block.core.slot):
|
||||
shard_slot_transition(state, beacon_state)
|
||||
shard_block_transition(state, beacon_state, block, validate_state_root=validate_state_root)
|
||||
```
|
||||
|
||||
```python
|
||||
def shard_slot_transition(state: ShardState, beacon_state: BeaconState) -> None:
|
||||
# Correct saved state root
|
||||
if state.most_recent_block_core.state_root == Hash():
|
||||
state.most_recent_block_core.state_root = hash_tree_root(state)
|
||||
|
||||
# Save states in history accumulator
|
||||
depth = 0
|
||||
h = hash_tree_root(state)
|
||||
while state.slot % 2**depth == 0 and depth < HISTORY_ACCUMULATOR_VECTOR:
|
||||
state.history_accumulator[depth] = h
|
||||
depth += 1
|
||||
|
||||
# Period transitions
|
||||
if (state.slot + 1) % (SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH * EPOCHS_PER_SHARD_PERIOD) == 0:
|
||||
epoch = compute_epoch_of_shard_slot(state.slot)
|
||||
earlier_committee = get_period_committee(
|
||||
beacon_state,
|
||||
get_shard_period_start_epoch(epoch, lookback=2),
|
||||
state.shard,
|
||||
)
|
||||
later_committee = get_period_committee(
|
||||
beacon_state,
|
||||
get_shard_period_start_epoch(epoch, lookback=1),
|
||||
state.shard,
|
||||
)
|
||||
state.receipt_root = hash_tree_root(List[ShardReceiptDelta, PLACEHOLDER]([
|
||||
ShardReceiptDelta(
|
||||
index=validator_index,
|
||||
reward_coefficient=state.earlier_committee_rewards[i],
|
||||
block_fee=state.earlier_committee_fees[i],
|
||||
)
|
||||
for i, validator_index in enumerate(earlier_committee)
|
||||
]))
|
||||
state.earlier_committee_rewards = state.later_committee_rewards
|
||||
state.earlier_committee_fees = state.later_committee_fees
|
||||
state.later_committee_rewards = [REWARD_COEFFICIENT_BASE for _ in range(len(later_committee))],
|
||||
state.later_committee_fees = [Gwei(0) for _ in range(len(later_committee))],
|
||||
else:
|
||||
state.receipt_root = Hash()
|
||||
state.slot += ShardSlot(1)
|
||||
```
|
||||
|
||||
```python
|
||||
def shard_block_transition(state: ShardState,
|
||||
beacon_state: BeaconState,
|
||||
block: ShardBlock,
|
||||
validate_state_root: bool) -> None:
|
||||
# Check slot number
|
||||
assert block.core.slot == state.slot
|
||||
|
||||
# Check parent block
|
||||
if block.core.parent_root != Hash():
|
||||
assert block.core.parent_root == hash_tree_root(state.most_recent_block_core)
|
||||
|
||||
# Calculate base reward
|
||||
total_balance = get_total_active_balance(beacon_state)
|
||||
base_reward = (
|
||||
REWARD_COEFFICIENT_BASE * BASE_REWARD_FACTOR // integer_squareroot(total_balance) // BASE_REWARDS_PER_EPOCH
|
||||
)
|
||||
# Check attestations
|
||||
attester_committee = get_persistent_committee(beacon_state, state.shard, block.core.slot)
|
||||
pubkeys = []
|
||||
attestations = 0
|
||||
|
||||
for i, validator_index in enumerate(attester_committee):
|
||||
if block.core.attester_bitfield[i]:
|
||||
pubkeys.append(beacon_state.validators[validator_index].pubkey)
|
||||
add_reward(state, beacon_state, validator_index, base_reward)
|
||||
attestations += 1
|
||||
|
||||
for i in range(len(attester_committee), TARGET_PERSISTENT_COMMITTEE_SIZE):
|
||||
assert block.core.attester_bitfield[i] is False or block.core.attester_bitfield[i] == 0 # TODO: FIX Bitvector
|
||||
|
||||
assert bls_verify(
|
||||
pubkey=bls_aggregate_pubkeys(pubkeys),
|
||||
message_hash=block.core.parent_root,
|
||||
signature=block.signatures.attestation_signature,
|
||||
domain=get_domain(beacon_state, DOMAIN_SHARD_ATTESTER, compute_epoch_of_shard_slot(block.core.slot))
|
||||
)
|
||||
|
||||
# Check proposer
|
||||
proposer_index = get_shard_block_proposer_index(beacon_state, state.shard, block.core.slot)
|
||||
assert proposer_index is not None
|
||||
add_reward(state, beacon_state, proposer_index, attestations * base_reward // PROPOSER_REWARD_QUOTIENT)
|
||||
assert bls_verify(
|
||||
pubkey=beacon_state.validators[proposer_index].pubkey,
|
||||
message_hash=hash_tree_root(block.core),
|
||||
signature=block.signatures.proposer_signature,
|
||||
domain=get_domain(beacon_state, DOMAIN_SHARD_PROPOSER, compute_epoch_of_shard_slot(block.core.slot)),
|
||||
)
|
||||
|
||||
# Process and update block data fees
|
||||
add_fee(state, beacon_state, proposer_index, state.basefee * len(block.core.data) // SHARD_BLOCK_SIZE_LIMIT)
|
||||
QUOTIENT = SHARD_BLOCK_SIZE_LIMIT * BASEFEE_ADJUSTMENT_FACTOR
|
||||
if len(block.core.data) > SHARD_BLOCK_SIZE_TARGET:
|
||||
state.basefee += Gwei(max(1, state.basefee * (len(block.core.data) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT))
|
||||
elif len(block.core.data) < SHARD_BLOCK_SIZE_TARGET:
|
||||
state.basefee -= Gwei(max(1, state.basefee * (len(block.core.data) - SHARD_BLOCK_SIZE_TARGET) // QUOTIENT))
|
||||
state.basefee = Gwei(max(
|
||||
1,
|
||||
min(
|
||||
EFFECTIVE_BALANCE_INCREMENT // EPOCHS_PER_SHARD_PERIOD // SHARD_SLOTS_PER_BEACON_SLOT * SLOTS_PER_EPOCH,
|
||||
state.basefee,
|
||||
)
|
||||
))
|
||||
|
||||
# Check total bytes
|
||||
state.total_bytes += len(block.core.data)
|
||||
assert block.core.total_bytes == state.total_bytes
|
||||
|
||||
# Update in-state block header
|
||||
state.most_recent_block_core = ShardBlockCore(
|
||||
slot=block.core.slot,
|
||||
beacon_chain_root=block.core.beacon_chain_root,
|
||||
parent_root=block.core.parent_root,
|
||||
data_root=hash_tree_root(block.core.data),
|
||||
state_root=Hash(),
|
||||
total_bytes=block.core.total_bytes,
|
||||
attester_bitfield=block.core.attester_bitfield,
|
||||
)
|
||||
|
||||
# Check state root
|
||||
if validate_state_root:
|
||||
assert block.core.state_root == hash_tree_root(state)
|
||||
```
|
||||
|
||||
### Beacon attestations
|
||||
|
||||
Let:
|
||||
|
||||
- `pre_state` is the `ShardState` before processing any blocks
|
||||
- `pre_state` be the `ShardState` before processing any blocks
|
||||
- `shard_blocks_or_state_roots` be the `Union[ShardBlock, Hash]` list such that `shard_blocks[slot]` is the canonical `ShardBlock` for shard `pre_state.shard` at slot `slot` if a block exists, or the post-state-root of processing state up to and including that slot if a block does not exist.
|
||||
- `beacon_state` be the canonical `BeaconState`
|
||||
- `valid_attestations` be the set of valid `Attestation` objects, recursively defined
|
||||
@ -594,12 +546,9 @@ def is_valid_beacon_attestation(pre_state: ShardState,
|
||||
blocks.append(shard_blocks_or_state_roots[slot])
|
||||
else:
|
||||
blocks.append(ShardBlock(
|
||||
core=ExtendedShardBlockCore(
|
||||
slot=slot,
|
||||
state_root=shard_blocks_or_state_roots[slot],
|
||||
total_bytes=pre_state.total_bytes,
|
||||
),
|
||||
signatures=ShardBlockSignatures(),
|
||||
slot=slot,
|
||||
state_root=shard_blocks_or_state_roots[slot],
|
||||
total_bytes=pre_state.total_bytes,
|
||||
))
|
||||
assert candidate.data.crosslink.data_root == compute_crosslink_data_root(blocks)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user