Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add proposal support to chain scan #5

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,20 @@ A single entry will be made in each of the attestation slashing protection and b

This prevents the validator from making attestations in the current epoch, and blocks in the current slot, because of [slashing protection conditions 2, 4, and 5](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3076.md#conditions).

##### Validator Activity Implications
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to different part of README

This may lead to some false positives from the slashing prevention detection component (i.e., messages that are not actually slashable are identified as slashable). **The validator *WILL* lose out on rewards because of inactivity in the current epoch.**

#### 3. `parse_chain` (Experimental)
This method regenerates the block and attestation component of the slashing protection file based on what is currently available in the block tree.

##### Specific Assumptions
- The beacon node providing the Eth2 API is fully synced.
- The current justified checkpoint epoch never decreases.

##### Description
The method currently only regenerates the attestation component of the slashing protection file. The block protection item will be filled with the current slot. The purpose of this method is to avoid any false positives from the slashing prevention detection component.

The slashing protection information is generated in the following way:
- Parse entire subtree descending from last justified block
- If signed attestations are present, then identify the attestation with the *largest target epoch* that the validator has signed. Then use the source epoch and target epoch values from that attestation to make a single entry in the `"signed_attestations"` field of the slashing protection file.
- If no signed attestations are found in this subtree, make a single entry in the `"signed_attestations"` field of the slashing protection file with `"source_epoch"` and `"target_epoch"` set to the current justified epoch.
- If a block is present that was proposed by the validator under consideration, use the slot of this block to make a single entry in the `"signed_blocks"` field of the slashing protection file.
- If a block is not found, make a single entry in the `"signed_blocks"` field of the slashing protection file with `"slot"` set to the current slot.

---

Expand Down
26 changes: 18 additions & 8 deletions rebuild_slashing_protection.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ def get_signed_attestations(block, head_state, validator_index, committee_cache,
return signed_attestations


def process_chain(head, justified_epoch, processed_blocks, best_attestations_in_chains, validator_index):
def process_chain(head, justified_epoch, processed_blocks, best_attestations_in_chains, proposal_slots, validator_index):
adiasg marked this conversation as resolved.
Show resolved Hide resolved
logging.info(f"Processing chain from head: {head}")
head_root = head["root"]
# The below block header fetching might cause problems sometimes
Expand All @@ -314,6 +314,11 @@ def process_chain(head, justified_epoch, processed_blocks, best_attestations_in_
block = fetch_block(current_block_root)
while current_block_root not in processed_blocks and block.slot > justified_epoch * SLOTS_PER_EPOCH:
logging.info(f'Processing Block - Slot: {block.slot}, Root: {current_block_root}')

if block.proposer_index == validator_index:
logging.info(f'Found proposal in current chain - Block - Slot: {block.slot}, Root: {current_block_root}')
proposal_slots.append(block.slot)
adiasg marked this conversation as resolved.
Show resolved Hide resolved

signed_attestations = get_signed_attestations(block, head_state, validator_index, committee_cache, head)
if signed_attestations:
logging.debug(f'Signed attestations in this block: {signed_attestations}')
Expand Down Expand Up @@ -357,13 +362,11 @@ def process_chain(head, justified_epoch, processed_blocks, best_attestations_in_
justified_epoch = int(finality_checkpoints["current_justified"]["epoch"])
processed_blocks = []
best_attestations_in_chains = []
proposal_slots = []
adiasg marked this conversation as resolved.
Show resolved Hide resolved

sorted(heads, key=lambda head: int(head["slot"]), reverse=True)
for head in heads:
process_chain(head, justified_epoch, processed_blocks, best_attestations_in_chains, validator_index)
best_attestation = max(best_attestations_in_chains, key=lambda att: att.data.target.epoch)
if compute_epoch_at_slot(best_attestation.data.slot) > compute_epoch_at_slot(int(head["slot"])):
break
ralexstokes marked this conversation as resolved.
Show resolved Hide resolved
process_chain(head, justified_epoch, processed_blocks, best_attestations_in_chains, proposal_slots, validator_index)
adiasg marked this conversation as resolved.
Show resolved Hide resolved

if best_attestations_in_chains:
best_attestation = max(best_attestations_in_chains, key=lambda att: att.data.target.epoch)
Expand All @@ -375,9 +378,16 @@ def process_chain(head, justified_epoch, processed_blocks, best_attestations_in_
best_attestation.data.target.epoch = justified_epoch
logging.info(f'Plugging in fake Attestation with Source Epoch: {best_attestation.data.source.epoch}, Target Epoch: {best_attestation.data.target.epoch}')

logging.info("This program does not rebuild block protection history yet. The highest known head slot will be plugged into the block protection component.")
best_block_slot = heads[0]["slot"]
logging.info(f'Plugging in fake Block with Slot: {best_block_slot}')
if proposal_slots:
best_block_slot = max(proposal_slots)
logging.info(f"Best proposal found with slot: {best_block_slot}")
adiasg marked this conversation as resolved.
Show resolved Hide resolved
else:
genesis_time = int(genesis["genesis_time"])
current_time = int(time.time())
current_slot = (current_time - genesis_time) // SECONDS_PER_SLOT
logging.info("No proposals found for this validator. Using the current slot instead - Slot: {current_slot}.")
best_block_slot = current_slot
logging.info(f'Plugging in fake Block with Slot: {best_block_slot}')

validator_protection_info = [generate_validator_protection_json(validator_pubkey, best_attestation.data.source.epoch, best_attestation.data.target.epoch, best_block_slot)]
write_protection_file(genesis_validators_root, validator_protection_info)