30 KiB
Ethereum 2.0 Sharding -- Beacon Chain changes
Notice: This document is a work-in-progress for researchers and implementers.
Table of contents
- Introduction
- Custom types
- Constants
- Preset
- Configuration
- Updated containers
- New containers
- Helper functions
Introduction
This document describes the extensions made to the Phase 0 design of The Beacon Chain to support data sharding, based on the ideas here and more broadly here, using KZG10 commitments to commit to data to remove any need for fraud proofs (and hence, safety-critical synchrony assumptions) in the design.
Custom types
We define the following Python custom types for type hinting and readability:
Name | SSZ equivalent | Description |
---|---|---|
Shard |
uint64 |
A shard number |
BLSCommitment |
Bytes48 |
A G1 curve point |
BLSPoint |
uint256 |
A number x in the range 0 <= x < MODULUS |
Constants
The following values are (non-configurable) constants used throughout the specification.
Misc
Name | Value | Notes |
---|---|---|
PRIMITIVE_ROOT_OF_UNITY |
5 |
Primitive root of unity of the BLS12_381 (inner) modulus |
DATA_AVAILABILITY_INVERSE_CODING_RATE |
2**1 (= 2) |
Factor by which samples are extended for data availability encoding |
POINTS_PER_SAMPLE |
uint64(2**3) (= 8) |
31 * 8 = 248 bytes |
MODULUS |
0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001 (curve order of BLS12_381) |
Domain types
Name | Value |
---|---|
DOMAIN_SHARD_PROPOSER |
DomainType('0x80000000') |
DOMAIN_SHARD_COMMITTEE |
DomainType('0x81000000') |
Shard Work Status
Name | Value | Notes |
---|---|---|
SHARD_WORK_UNCONFIRMED |
0 |
Unconfirmed, nullified after confirmation time elapses |
SHARD_WORK_CONFIRMED |
1 |
Confirmed, reduced to just the commitment |
SHARD_WORK_PENDING |
2 |
Pending, a list of competing headers |
Preset
Misc
Name | Value | Notes |
---|---|---|
MAX_SHARDS |
uint64(2**10) (= 1,024) |
Theoretical max shard count (used to determine data structure sizes) |
GASPRICE_ADJUSTMENT_COEFFICIENT |
uint64(2**3) (= 8) |
Gasprice may decrease/increase by at most exp(1 / this value) per epoch |
MAX_SHARD_PROPOSER_SLASHINGS |
2**4 (= 16) |
Maximum amount of shard proposer slashing operations per block |
MAX_SHARD_HEADERS_PER_SHARD |
4 |
|
SHARD_STATE_MEMORY_SLOTS |
uint64(2**8) (= 256) |
Number of slots for which shard commitments and confirmation status is directly available in the state |
Shard block samples
Name | Value | Notes |
---|---|---|
MAX_SAMPLES_PER_BLOCK |
uint64(2**11) (= 2,048) |
248 * 2,048 = 507,904 bytes |
TARGET_SAMPLES_PER_BLOCK |
uint64(2**10) (= 1,024) |
248 * 1,024 = 253,952 bytes |
Precomputed size verification points
Name | Value |
---|---|
G1_SETUP |
Type List[G1] . The G1-side trusted setup [G, G*s, G*s**2....] ; note that the first point is the generator. |
G2_SETUP |
Type List[G2] . The G2-side trusted setup [G, G*s, G*s**2....] |
ROOT_OF_UNITY |
pow(PRIMITIVE_ROOT_OF_UNITY, (MODULUS - 1) // int(MAX_SAMPLES_PER_BLOCK * POINTS_PER_SAMPLE), MODULUS) |
Gwei values
Name | Value | Unit | Description |
---|---|---|---|
MAX_GASPRICE |
Gwei(2**33) (= 8,589,934,592) |
Gwei | Max gasprice charged for a TARGET-sized shard block |
MIN_GASPRICE |
Gwei(2**3) (= 8) |
Gwei | Min gasprice charged for a TARGET-sized shard block |
Configuration
Name | Value | Notes |
---|---|---|
INITIAL_ACTIVE_SHARDS |
uint64(2**6) (= 64) |
Initial shard count |
Updated containers
The following containers have updated definitions to support Sharding.
AttestationData
class AttestationData(Container):
slot: Slot
index: CommitteeIndex
# LMD GHOST vote
beacon_block_root: Root
# FFG vote
source: Checkpoint
target: Checkpoint
# Shard header root
shard_header_root: Root # [New in Sharding]
BeaconBlockBody
class BeaconBlockBody(merge.BeaconBlockBody): # [extends The Merge block body]
shard_proposer_slashings: List[ShardProposerSlashing, MAX_SHARD_PROPOSER_SLASHINGS]
shard_headers: List[SignedShardBlobHeader, MAX_SHARDS * MAX_SHARD_HEADERS_PER_SHARD]
BeaconState
class BeaconState(merge.BeaconState):
# [Updated fields] (Warning: this changes with Altair, Sharding will rebase to use participation-flags)
previous_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
current_epoch_attestations: List[PendingAttestation, MAX_ATTESTATIONS * SLOTS_PER_EPOCH]
# [New fields]
# A ring buffer of the latest slots, with information per active shard.
shard_buffer: Vector[List[ShardWork, MAX_SHARDS], SHARD_STATE_MEMORY_SLOTS]
shard_gasprice: uint64
New containers
The shard data itself is network-layer only, and can be found in the P2P specification. The beacon chain registers just the commitments of the shard data.
DataCommitment
class DataCommitment(Container):
# KZG10 commitment to the data
point: BLSCommitment
# Length of the data in samples
length: uint64
ShardBlobBodySummary
class ShardBlobBodySummary(Container):
# The actual data commitment
commitment: DataCommitment
# Proof that the degree < commitment.length
degree_proof: BLSCommitment
# Hash-tree-root as summary of the data field
data_root: Root
# Latest block root of the Beacon Chain, before shard_blob.slot
beacon_block_root: Root
ShardBlobHeader
class ShardBlobHeader(Container):
# Slot and shard that this header is intended for
slot: Slot
shard: Shard
# SSZ-summary of ShardBlobBody
body_summary: ShardBlobBodySummary
# Proposer of the shard-blob
proposer_index: ValidatorIndex
SignedShardBlobHeader
class SignedShardBlobHeader(Container):
message: ShardBlobHeader
signature: BLSSignature
PendingShardHeader
class PendingShardHeader(Container):
# KZG10 commitment to the data
commitment: DataCommitment
# hash_tree_root of the ShardHeader (stored so that attestations can be checked against it)
root: Root
# Who voted for the header
votes: Bitlist[MAX_VALIDATORS_PER_COMMITTEE]
# Sum of effective balances of votes
weight: Gwei
# When the header was last updated, as reference for weight accuracy
update_slot: Slot
ShardBlobReference
class ShardBlobReference(Container):
# Slot and shard that this reference is intended for
slot: Slot
shard: Shard
# Hash-tree-root of ShardBlobBody
body_root: Root
# Proposer of the shard-blob
proposer_index: ValidatorIndex
SignedShardBlobReference
class SignedShardBlobReference(Container):
message: ShardBlobReference
signature: BLSSignature
ShardProposerSlashing
class ShardProposerSlashing(Container):
signed_reference_1: SignedShardBlobReference
signed_reference_2: SignedShardBlobReference
ShardWork
class ShardWork(Container):
# Upon confirmation the data is reduced to just the header.
status: Union[ # See Shard Work Status enum
None, # SHARD_WORK_UNCONFIRMED
DataCommitment, # SHARD_WORK_CONFIRMED
List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] # SHARD_WORK_PENDING
]
Helper functions
Misc
next_power_of_two
def next_power_of_two(x: int) -> int:
return 2 ** ((x - 1).bit_length())
compute_previous_slot
def compute_previous_slot(slot: Slot) -> Slot:
if slot > 0:
return Slot(slot - 1)
else:
return Slot(0)
compute_updated_gasprice
def compute_updated_gasprice(prev_gasprice: Gwei, shard_block_length: uint64, adjustment_quotient: uint64) -> Gwei:
if shard_block_length > TARGET_SAMPLES_PER_BLOCK:
delta = max(1, prev_gasprice * (shard_block_length - TARGET_SAMPLES_PER_BLOCK)
// TARGET_SAMPLES_PER_BLOCK // adjustment_quotient)
return min(prev_gasprice + delta, MAX_GASPRICE)
else:
delta = max(1, prev_gasprice * (TARGET_SAMPLES_PER_BLOCK - shard_block_length)
// TARGET_SAMPLES_PER_BLOCK // adjustment_quotient)
return max(prev_gasprice, MIN_GASPRICE + delta) - delta
compute_committee_source_epoch
def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch:
"""
Return the source epoch for computing the committee.
"""
source_epoch = Epoch(epoch - epoch % period)
if source_epoch >= period:
source_epoch -= period # `period` epochs lookahead
return source_epoch
Beacon state accessors
Updated get_committee_count_per_slot
def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64:
"""
Return the number of committees in each slot for the given ``epoch``.
"""
return max(uint64(1), min(
get_active_shard_count(state, epoch),
uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
))
get_active_shard_count
def get_active_shard_count(state: BeaconState, epoch: Epoch) -> uint64:
"""
Return the number of active shards.
Note that this puts an upper bound on the number of committees per slot.
"""
return INITIAL_ACTIVE_SHARDS
get_shard_committee
def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -> Sequence[ValidatorIndex]:
"""
Return the shard committee of the given ``epoch`` of the given ``shard``.
"""
source_epoch = compute_committee_source_epoch(epoch, SHARD_COMMITTEE_PERIOD)
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE)
return compute_committee(
indices=active_validator_indices,
seed=seed,
index=shard,
count=get_active_shard_count(beacon_state, epoch),
)
compute_proposer_index
Updated version to get a proposer index that will only allow proposers with a certain minimum balance, ensuring that the balance is always sufficient to cover gas costs.
def compute_proposer_index(beacon_state: BeaconState,
indices: Sequence[ValidatorIndex],
seed: Bytes32,
min_effective_balance: Gwei = Gwei(0)) -> ValidatorIndex:
"""
Return from ``indices`` a random index sampled by effective balance.
"""
assert len(indices) > 0
MAX_RANDOM_BYTE = 2**8 - 1
i = uint64(0)
total = uint64(len(indices))
while True:
candidate_index = indices[compute_shuffled_index(i % total, total, seed)]
random_byte = hash(seed + uint_to_bytes(uint64(i // 32)))[i % 32]
effective_balance = beacon_state.validators[candidate_index].effective_balance
if effective_balance <= min_effective_balance:
continue
if effective_balance * MAX_RANDOM_BYTE >= MAX_EFFECTIVE_BALANCE * random_byte:
return candidate_index
i += 1
get_shard_proposer_index
def get_shard_proposer_index(beacon_state: BeaconState, slot: Slot, shard: Shard) -> ValidatorIndex:
"""
Return the proposer's index of shard block at ``slot``.
"""
epoch = compute_epoch_at_slot(slot)
committee = get_shard_committee(beacon_state, epoch, shard)
seed = hash(get_seed(beacon_state, epoch, DOMAIN_SHARD_PROPOSER) + uint_to_bytes(slot))
# Proposer must have sufficient balance to pay for worst case fee burn
EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION = (
EFFECTIVE_BALANCE_INCREMENT - EFFECTIVE_BALANCE_INCREMENT
* HYSTERESIS_DOWNWARD_MULTIPLIER // HYSTERESIS_QUOTIENT
)
min_effective_balance = (
beacon_state.shard_gasprice * MAX_SAMPLES_PER_BLOCK // TARGET_SAMPLES_PER_BLOCK
+ EFFECTIVE_BALANCE_MAX_DOWNWARD_DEVIATION
)
return compute_proposer_index(beacon_state, committee, seed, min_effective_balance)
get_start_shard
def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
"""
Return the start shard at ``slot``.
"""
epoch = compute_epoch_at_slot(Slot(_slot))
committee_count = get_committee_count_per_slot(state, epoch)
active_shard_count = get_active_shard_count(state, epoch)
return committee_count * slot % active_shard_count
compute_shard_from_committee_index
def compute_shard_from_committee_index(state: BeaconState, slot: Slot, index: CommitteeIndex) -> Shard:
active_shards = get_active_shard_count(state, compute_epoch_at_slot(slot))
assert index < active_shards
return Shard((index + get_start_shard(state, slot)) % active_shards)
compute_committee_index_from_shard
def compute_committee_index_from_shard(state: BeaconState, slot: Slot, shard: Shard) -> CommitteeIndex:
epoch = compute_epoch_at_slot(slot)
active_shards = get_active_shard_count(state, epoch)
index = CommitteeIndex((active_shards + shard - get_start_shard(state, slot)) % active_shards)
assert index < get_committee_count_per_slot(state, epoch)
return index
Block processing
def process_block(state: BeaconState, block: BeaconBlock) -> None:
process_block_header(state, block)
process_randao(state, block.body)
process_eth1_data(state, block.body)
process_operations(state, block.body) # [Modified in Sharding]
process_sync_aggregate(state, block.body.sync_aggregate)
# is_execution_enabled is omitted, execution is enabled by default.
process_execution_payload(state, block.body.execution_payload, EXECUTION_ENGINE)
Operations
def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
# Verify that outstanding deposits are processed up to the maximum number of deposits
assert len(body.deposits) == min(MAX_DEPOSITS, state.eth1_data.deposit_count - state.eth1_deposit_index)
def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -> None:
for operation in operations:
fn(state, operation)
for_ops(body.proposer_slashings, process_proposer_slashing)
for_ops(body.attester_slashings, process_attester_slashing)
# New shard proposer slashing processing
for_ops(body.shard_proposer_slashings, process_shard_proposer_slashing)
# Limit is dynamic based on active shard count
assert len(body.shard_headers) <= MAX_SHARD_HEADERS_PER_SHARD * get_active_shard_count(state, get_current_epoch(state))
for_ops(body.shard_headers, process_shard_header)
# New attestation processing
for_ops(body.attestations, process_attestation)
for_ops(body.deposits, process_deposit)
for_ops(body.voluntary_exits, process_voluntary_exit)
Extended Attestation processing
def process_attestation(state: BeaconState, attestation: Attestation) -> None:
altair.process_attestation(state, attestation)
update_pending_shard_work(state, attestation)
def update_pending_shard_work(state: BeaconState, attestation: Attestation) -> None:
attestation_shard = compute_shard_from_committee_index(
state,
attestation.data.slot,
attestation.data.index,
)
buffer_index = attestation.data.slot % SHARD_STATE_MEMORY_SLOTS
committee_work = state.shard_buffer[buffer_index][attestation_shard]
# Skip attestation vote accounting if the header is not pending
if committee_work.status.selector != SHARD_WORK_PENDING:
# TODO In Altair: set participation bit flag, if attestation matches winning header.
return
current_headers: Sequence[PendingShardHeader] = committee_work.status.value
# Find the corresponding header, abort if it cannot be found
header_index = [header.root for header in current_headers].index(attestation.data.shard_header_root)
pending_header: PendingShardHeader = current_headers[header_index]
full_committee = get_beacon_committee(state, attestation.data.slot, attestation.data.index)
# The weight may be outdated if it is not the initial weight, and from a previous epoch
if pending_header.weight != 0 and compute_epoch_at_slot(pending_header.update_slot) < get_current_epoch(state):
pending_header.weight = sum(state.validators[index].effective_balance for index, bit
in zip(full_committee, pending_header.votes) if bit)
pending_header.update_slot = state.slot
full_committee_balance = Gwei(0)
# Update votes bitfield in the state, update weights
for i, bit in enumerate(attestation.aggregation_bits):
weight = state.validators[full_committee[i]].effective_balance
full_committee_balance += weight
if bit:
if not pending_header.votes[i]:
pending_header.weight += weight
pending_header.votes[i] = True
# Check if the PendingShardHeader is eligible for expedited confirmation, requiring 2/3 of balance attesting
if pending_header.weight * 3 >= full_committee_balance * 2:
# TODO In Altair: set participation bit flag for voters of this early winning header
if pending_header.commitment == DataCommitment():
# The committee voted to not confirm anything
state.shard_buffer[buffer_index][attestation_shard].status.change(
selector=SHARD_WORK_UNCONFIRMED,
value=None,
)
else:
state.shard_buffer[buffer_index][attestation_shard].status.change(
selector=SHARD_WORK_CONFIRMED,
value=pending_header.commitment,
)
process_shard_header
def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeader) -> None:
header = signed_header.message
# Verify the header is not 0, and not from the future.
assert Slot(0) < header.slot <= state.slot
header_epoch = compute_epoch_at_slot(header.slot)
# Verify that the header is within the processing time window
assert header_epoch in [get_previous_epoch(state), get_current_epoch(state)]
# Verify that the shard is active
assert header.shard < get_active_shard_count(state, header_epoch)
# Verify that the block root matches,
# to ensure the header will only be included in this specific Beacon Chain sub-tree.
assert header.body_summary.beacon_block_root == get_block_root_at_slot(state, header.slot - 1)
# Check that this data is still pending
committee_work = state.shard_buffer[header.slot % SHARD_STATE_MEMORY_SLOTS][header.shard]
assert committee_work.status.selector == SHARD_WORK_PENDING
# Check that this header is not yet in the pending list
current_headers: List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD] = committee_work.status.value
header_root = hash_tree_root(header)
assert header_root not in [pending_header.root for pending_header in current_headers]
# Verify proposer
assert header.proposer_index == get_shard_proposer_index(state, header.slot, header.shard)
# Verify signature
signing_root = compute_signing_root(header, get_domain(state, DOMAIN_SHARD_PROPOSER))
assert bls.Verify(state.validators[header.proposer_index].pubkey, signing_root, signed_header.signature)
# Verify the length by verifying the degree.
body_summary = header.body_summary
if body_summary.commitment.length == 0:
assert body_summary.degree_proof == G1_SETUP[0]
assert (
bls.Pairing(body_summary.degree_proof, G2_SETUP[0])
== bls.Pairing(body_summary.commitment.point, G2_SETUP[-body_summary.commitment.length])
)
# Initialize the pending header
index = compute_committee_index_from_shard(state, header.slot, header.shard)
committee_length = len(get_beacon_committee(state, header.slot, index))
initial_votes = Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length)
pending_header = PendingShardHeader(
commitment=body_summary.commitment,
root=header_root,
votes=initial_votes,
weight=0,
update_slot=state.slot,
)
# Include it in the pending list
current_headers.append(pending_header)
The degree proof works as follows. For a block B
with length l
(so l
values in [0...l - 1]
, seen as a polynomial B(X)
which takes these values),
the length proof is the commitment to the polynomial B(X) * X**(MAX_DEGREE + 1 - l)
,
where MAX_DEGREE
is the maximum power of s
available in the setup, which is MAX_DEGREE = len(G2_SETUP) - 1
.
The goal is to ensure that a proof can only be constructed if deg(B) < l
(there are not hidden higher-order terms in the polynomial, which would thwart reconstruction).
process_shard_proposer_slashing
def process_shard_proposer_slashing(state: BeaconState, proposer_slashing: ShardProposerSlashing) -> None:
reference_1 = proposer_slashing.signed_reference_1.message
reference_2 = proposer_slashing.signed_reference_2.message
# Verify header slots match
assert reference_1.slot == reference_2.slot
# Verify header shards match
assert reference_1.shard == reference_2.shard
# Verify header proposer indices match
assert reference_1.proposer_index == reference_2.proposer_index
# Verify the headers are different (i.e. different body)
assert reference_1 != reference_2
# Verify the proposer is slashable
proposer = state.validators[reference_1.proposer_index]
assert is_slashable_validator(proposer, get_current_epoch(state))
# Verify signatures
for signed_header in (proposer_slashing.signed_reference_1, proposer_slashing.signed_reference_2):
domain = get_domain(state, DOMAIN_SHARD_PROPOSER, compute_epoch_at_slot(signed_header.message.slot))
signing_root = compute_signing_root(signed_header.message, domain)
assert bls.Verify(proposer.pubkey, signing_root, signed_header.signature)
slash_validator(state, reference_1.proposer_index)
Epoch transition
This epoch transition overrides the Merge epoch transition:
def process_epoch(state: BeaconState) -> None:
# Sharding pre-processing
process_pending_shard_confirmations(state)
charge_confirmed_shard_fees(state)
reset_pending_shard_work(state)
# Base functionality
process_justification_and_finalization(state)
process_inactivity_updates(state)
process_rewards_and_penalties(state)
process_registry_updates(state)
process_slashings(state)
process_eth1_data_reset(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
process_historical_roots_update(state)
process_participation_flag_updates(state)
process_sync_committee_updates(state)
process_pending_shard_confirmations
def process_pending_shard_confirmations(state: BeaconState) -> None:
# Pending header processing applies to the previous epoch.
# Skip if `GENESIS_EPOCH` because no prior epoch to process.
if get_current_epoch(state) == GENESIS_EPOCH:
return
previous_epoch = get_previous_epoch(state)
previous_epoch_start_slot = compute_start_slot_at_epoch(previous_epoch)
# Mark stale headers as unconfirmed
for slot in range(previous_epoch_start_slot, previous_epoch_start_slot + SLOTS_PER_EPOCH):
buffer_index = slot % SHARD_STATE_MEMORY_SLOTS
for shard_index in range(len(state.shard_buffer[buffer_index])):
committee_work = state.shard_buffer[buffer_index][shard_index]
if committee_work.status.selector == SHARD_WORK_PENDING:
winning_header = max(committee_work.status.value, key=lambda header: header.weight)
# TODO In Altair: set participation bit flag of voters for winning header
if winning_header.commitment == DataCommitment():
committee_work.status.change(selector=SHARD_WORK_UNCONFIRMED, value=None)
else:
committee_work.status.change(selector=SHARD_WORK_CONFIRMED, value=winning_header.commitment)
charge_confirmed_shard_fees
def charge_confirmed_shard_fees(state: BeaconState) -> None:
new_gasprice = state.shard_gasprice
previous_epoch = get_previous_epoch(state)
previous_epoch_start_slot = compute_start_slot_at_epoch(previous_epoch)
adjustment_quotient = (
get_active_shard_count(state, previous_epoch)
* SLOTS_PER_EPOCH * GASPRICE_ADJUSTMENT_COEFFICIENT
)
# Iterate through confirmed shard-headers
for slot in range(previous_epoch_start_slot, previous_epoch_start_slot + SLOTS_PER_EPOCH):
buffer_index = slot % SHARD_STATE_MEMORY_SLOTS
for shard_index in range(len(state.shard_buffer[buffer_index])):
committee_work = state.shard_buffer[buffer_index][shard_index]
if committee_work.status.selector == SHARD_WORK_CONFIRMED:
commitment: DataCommitment = committee_work.status.value
# Charge EIP 1559 fee
proposer = get_shard_proposer_index(state, slot, Shard(shard_index))
fee = (
(state.shard_gasprice * commitment.length)
// TARGET_SAMPLES_PER_BLOCK
)
decrease_balance(state, proposer, fee)
# Track updated gas price
new_gasprice = compute_updated_gasprice(
new_gasprice,
commitment.length,
adjustment_quotient,
)
state.shard_gasprice = new_gasprice
reset_pending_shard_work
def reset_pending_shard_work(state: BeaconState) -> None:
# Add dummy "empty" PendingShardHeader (default vote if no shard header is available)
next_epoch = get_current_epoch(state) + 1
next_epoch_start_slot = compute_start_slot_at_epoch(next_epoch)
committees_per_slot = get_committee_count_per_slot(state, next_epoch)
active_shards = get_active_shard_count(state, next_epoch)
for slot in range(next_epoch_start_slot, next_epoch_start_slot + SLOTS_PER_EPOCH):
buffer_index = slot % SHARD_STATE_MEMORY_SLOTS
# Reset the shard work tracking
state.shard_buffer[buffer_index] = [ShardWork() for _ in range(active_shards)]
start_shard = get_start_shard(state, slot)
for committee_index in range(committees_per_slot):
shard = (start_shard + committee_index) % active_shards
# a committee is available, initialize a pending shard-header list
committee_length = len(get_beacon_committee(state, slot, CommitteeIndex(committee_index)))
state.shard_buffer[buffer_index][shard].status.change(
selector=SHARD_WORK_PENDING,
value=List[PendingShardHeader, MAX_SHARD_HEADERS_PER_SHARD](
PendingShardHeader(
commitment=DataCommitment(),
root=Root(),
votes=Bitlist[MAX_VALIDATORS_PER_COMMITTEE]([0] * committee_length),
weight=0,
update_slot=slot,
)
)
)
# a shard without committee available defaults to SHARD_WORK_UNCONFIRMED.