From 414ef614cb8f0d19c87d3e31c4c9b1addcb6bdb2 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Thu, 27 May 2021 15:13:13 +0300 Subject: [PATCH 1/3] Handle the case when a shard may not have a committee at slot. Block is invalid if contains ShardBlobHeader lacking committee Reject Gossip ShardBlobHeader and ShardBlob messages which lacks committee --- specs/sharding/beacon-chain.md | 18 +++++++++++++++--- specs/sharding/p2p-interface.md | 4 ++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index 5522e044d..2a0695c49 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -455,8 +455,15 @@ def compute_shard_from_committee_index(state: BeaconState, slot: Slot, index: Co ```python def compute_committee_index_from_shard(state: BeaconState, slot: Slot, shard: Shard) -> CommitteeIndex: + """ + Returns either committee index for ``shard`` at ``slot`` or ``None`` if no committee + """ active_shards = get_active_shard_count(state, compute_epoch_at_slot(slot)) - return CommitteeIndex((active_shards + shard - get_start_shard(state, slot)) % active_shards) + index = (active_shards + shard - get_start_shard(state, slot)) % active_shards + if index >= get_committee_count_per_slot(state, compute_epoch_at_slot(slot)): + return None + else: + return CommitteeIndex(index) ``` @@ -559,6 +566,7 @@ def update_pending_votes(state: BeaconState, attestation: Attestation) -> None: def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeader) -> None: header = signed_header.message + committee_index = compute_committee_index_from_shard(state, header.slot, header.shard) # 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) @@ -566,6 +574,8 @@ def process_shard_header(state: BeaconState, 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 shard has a committee at slot + assert committee_index is not None # 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) @@ -595,8 +605,7 @@ def process_shard_header(state: BeaconState, assert header_root not in [pending_header.root for pending_header in pending_headers] # Include it in the pending list - index = compute_committee_index_from_shard(state, header.slot, header.shard) - committee_length = len(get_beacon_committee(state, header.slot, index)) + committee_length = len(get_beacon_committee(state, header.slot, committee_index)) pending_headers.append(PendingShardHeader( slot=header.slot, shard=header.shard, @@ -693,6 +702,9 @@ def process_pending_headers(state: BeaconState) -> None: # The entire committee (and its balance) index = compute_committee_index_from_shard(state, slot, shard) + if index is None: + # the shard had no committee on this slot + continue full_committee = get_beacon_committee(state, slot, index) # The set of voters who voted for each header (and their total balances) voting_sets = [ diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index 47ed52970..458c9985b 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -117,6 +117,8 @@ The following validations MUST pass before forwarding the `signed_blob` (with in (a client MAY queue future blobs for processing at the appropriate slot). - _[IGNORE]_ The `blob` is new enough to be still be processed -- i.e. validate that `compute_epoch_at_slot(blob.slot) >= get_previous_epoch(state)` +- _[REJECT]_ The shard should have a committee at slot -- + i.e. validate that `compute_committee_index_from_shard(state, blob.slot, blob.shard) is not None` - _[REJECT]_ The shard blob is for the correct subnet -- i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id` - _[IGNORE]_ The blob is the first blob with valid signature received for the `(blob.proposer_index, blob.slot, blob.shard)` combination. @@ -141,6 +143,8 @@ The following validations MUST pass before forwarding the `signed_shard_header` - _[IGNORE]_ The `header` is new enough to be still be processed -- i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` - _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination. +- _[REJECT]_ The shard should have a committee at slot -- + i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard) is not None` - _[REJECT]_ The proposer signature, `signed_shard_header.signature`, is valid with respect to the `proposer_index` pubkey. - _[REJECT]_ The header is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`). From 071abfa846b9c4fc0bc141df8492938663c95ae5 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 4 Jun 2021 18:44:35 +0300 Subject: [PATCH 2/3] Revert beacon-chain changes as they are handled in PR #2455 --- specs/sharding/beacon-chain.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/specs/sharding/beacon-chain.md b/specs/sharding/beacon-chain.md index ae9177b5b..a15a002e4 100644 --- a/specs/sharding/beacon-chain.md +++ b/specs/sharding/beacon-chain.md @@ -456,15 +456,8 @@ def compute_shard_from_committee_index(state: BeaconState, slot: Slot, index: Co ```python def compute_committee_index_from_shard(state: BeaconState, slot: Slot, shard: Shard) -> CommitteeIndex: - """ - Returns either committee index for ``shard`` at ``slot`` or ``None`` if no committee - """ active_shards = get_active_shard_count(state, compute_epoch_at_slot(slot)) - index = (active_shards + shard - get_start_shard(state, slot)) % active_shards - if index >= get_committee_count_per_slot(state, compute_epoch_at_slot(slot)): - return None - else: - return CommitteeIndex(index) + return CommitteeIndex((active_shards + shard - get_start_shard(state, slot)) % active_shards) ``` @@ -567,7 +560,6 @@ def update_pending_votes(state: BeaconState, attestation: Attestation) -> None: def process_shard_header(state: BeaconState, signed_header: SignedShardBlobHeader) -> None: header = signed_header.message - committee_index = compute_committee_index_from_shard(state, header.slot, header.shard) # 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) @@ -575,8 +567,6 @@ def process_shard_header(state: BeaconState, 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 shard has a committee at slot - assert committee_index is not None # 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) @@ -606,7 +596,8 @@ def process_shard_header(state: BeaconState, assert header_root not in [pending_header.root for pending_header in pending_headers] # Include it in the pending list - committee_length = len(get_beacon_committee(state, header.slot, committee_index)) + index = compute_committee_index_from_shard(state, header.slot, header.shard) + committee_length = len(get_beacon_committee(state, header.slot, index)) pending_headers.append(PendingShardHeader( slot=header.slot, shard=header.shard, @@ -703,9 +694,6 @@ def process_pending_headers(state: BeaconState) -> None: # The entire committee (and its balance) index = compute_committee_index_from_shard(state, slot, shard) - if index is None: - # the shard had no committee on this slot - continue full_committee = get_beacon_committee(state, slot, index) # The set of voters who voted for each header (and their total balances) voting_sets = [ From 0ae9a85b17de320f7116e7ccd052e72eb29aa1e8 Mon Sep 17 00:00:00 2001 From: Anton Nashatyrev Date: Fri, 4 Jun 2021 18:48:09 +0300 Subject: [PATCH 3/3] Adopt shard_blob gossip validation on top of PR #2455: here the `compute_committee_index_from_shard` raises an error if no committee assigned to a shard --- specs/sharding/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/sharding/p2p-interface.md b/specs/sharding/p2p-interface.md index 9ff00b8cf..51dbfd5a6 100644 --- a/specs/sharding/p2p-interface.md +++ b/specs/sharding/p2p-interface.md @@ -125,7 +125,7 @@ The following validations MUST pass before forwarding the `signed_blob` (with in - _[IGNORE]_ The `blob` is new enough to be still be processed -- i.e. validate that `compute_epoch_at_slot(blob.slot) >= get_previous_epoch(state)` - _[REJECT]_ The shard should have a committee at slot -- - i.e. validate that `compute_committee_index_from_shard(state, blob.slot, blob.shard) is not None` + i.e. validate that `compute_committee_index_from_shard(state, blob.slot, blob.shard)` doesn't raise an error - _[REJECT]_ The shard blob is for the correct subnet -- i.e. `compute_subnet_for_shard_blob(state, blob.slot, blob.shard) == subnet_id` - _[IGNORE]_ The blob is the first blob with valid signature received for the `(blob.proposer_index, blob.slot, blob.shard)` combination. @@ -155,7 +155,7 @@ The following validations MUST pass before forwarding the `signed_shard_blob_hea i.e. validate that `compute_epoch_at_slot(header.slot) >= get_previous_epoch(state)` - _[IGNORE]_ The header is the first header with valid signature received for the `(header.proposer_index, header.slot, header.shard)` combination. - _[REJECT]_ The shard should have a committee at slot -- - i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard) is not None` + i.e. validate that `compute_committee_index_from_shard(state, header.slot, header.shard)` doesn't raise an error - _[REJECT]_ The proposer signature, `signed_shard_blob_header.signature`, is valid with respect to the `proposer_index` pubkey. - _[REJECT]_ The header is proposed by the expected `proposer_index` for the block's slot in the context of the current shuffling (defined by `header.body_summary.beacon_block_root`/`slot`).