Skip to content

Commit

Permalink
input generator and short readme #46
Browse files Browse the repository at this point in the history
  • Loading branch information
SK0M0R0H committed Aug 11, 2023
1 parent f1ddc2a commit e1f5265
Show file tree
Hide file tree
Showing 7 changed files with 350 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,5 @@ build/
cmake-build-debug/
# configuration directory with Proof Market credentials
.config

__pycache__/
70 changes: 70 additions & 0 deletions storage-proof-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Consensus Layer Storage Proofs

Here is an example of an application for transferring data from the Consensus Layer (CL) to the Execution Layer (EL) in the Ethereum network.
This transfer allows access to historical Ethereum data and CL information such as validators registry.

Note: for now, EL does not have access to the root of CS's block. However, [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788) fixes this.

# How does it work?
Here is CL's block definition:
```
class BeaconBlock(Container):
slot: Slot
proposer_index: ValidatorIndex
parent_root: Root
state_root: Root
body: BeaconBlockBody
```

`state_root` is a Merkle tree root (based on sha2-256) for `BeaconState`.
Beacon State defines the whole state of the network and can be used to obtain historical data.

```
class BeaconState(Container):
# Versioning
genesis_time: uint64
genesis_validators_root: Root
slot: Slot
fork: Fork
# History
latest_block_header: BeaconBlockHeader
block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT]
historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] # Frozen in Capella, replaced by historical_summaries
# Eth1
eth1_data: Eth1Data
eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH]
eth1_deposit_index: uint64
# Registry
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] # Per-epoch sums of slashed effective balances
# Participation
previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] # [Modified in Altair]
# Finality
justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] # Bit set for every recent justified epoch
previous_justified_checkpoint: Checkpoint
current_justified_checkpoint: Checkpoint
finalized_checkpoint: Checkpoint
# Inactivity
inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] # [New in Altair]
# Sync
current_sync_committee: SyncCommittee # [New in Altair]
next_sync_committee: SyncCommittee # [New in Altair]
# Execution
latest_execution_payload_header: ExecutionPayloadHeader # [New in Bellatrix]
# Withdrawals
next_withdrawal_index: WithdrawalIndex # [New in Capella]
next_withdrawal_validator_index: ValidatorIndex # [New in Capella]
# Deep history valid from Capella onwards
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] # [New in Capella]
```

Historical data for blocks produced before Capella update is located in 'historical_roots'.
The newer data is located in `historical_summaries`.
More information about Beacon State structure can be found [here](https://eth2book.info/capella/part3/containers/state/).

185 changes: 185 additions & 0 deletions storage-proof-app/consensus_layer_ssz.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
from ssz.hashable_container import HashableContainer
from ssz.sedes import (
Bitlist,
Bitvector,
ByteVector,
List,
Vector,
boolean,
byte,
bytes4,
bytes32,
bytes48,
uint8,
uint64,
uint256
)

import constants

Hash32 = bytes32
Root = bytes32
Gwei = uint64
Epoch = uint64
Slot = uint64
CommitteeIndex = uint64
ValidatorIndex = uint64
BLSPubkey = bytes48
ExecutionAddress = ByteVector(20)
WithdrawalIndex = uint64
ParticipationFlags = uint8
Version = bytes4

class Fork(HashableContainer):
fields = [
("previous_version", Version),
("current_version", Version),
("epoch", Epoch) # Epoch of latest fork
]


class Checkpoint(HashableContainer):
fields = [
("epoch", Epoch),
("root", Root)
]


class BeaconBlockHeader(HashableContainer):
fields = [
("slot", Slot),
("proposer_index", ValidatorIndex),
("parent_root", Root),
("state_root", Root),
("body_root", Root),
]


class Eth1Data(HashableContainer):
fields = [
("deposit_root", Root),
("deposit_count", uint64),
("block_hash", Hash32),
]


class Validator(HashableContainer):
fields = [
("pubkey", BLSPubkey),
("withdrawal_credentials", bytes32), # Commitment to pubkey for withdrawals
("effective_balance", Gwei), # Balance at stake
("slashed", boolean),
# Status epochs
("activation_eligibility_epoch", Epoch), # When criteria for activation were met
("activation_epoch", Epoch),
("exit_epoch", Epoch),
("withdrawable_epoch", Epoch), # When validator can withdraw funds
]


class AttestationData(HashableContainer):
fields = [
("slot", Slot),
("index", CommitteeIndex),
("beacon_block_root", Root),
("source", Checkpoint),
("target", Checkpoint),
]


class PendingAttestation(HashableContainer):
fields = [
("aggregation_bits", Bitlist(constants.MAX_VALIDATORS_PER_COMMITTEE)),
("data", AttestationData),
("inclusion_delay", Slot),
("proposer_index", ValidatorIndex),
]


class SyncCommittee(HashableContainer):
fields = [
("pubkeys", Vector(BLSPubkey, constants.SYNC_COMMITTEE_SIZE)),
("aggregate_pubkey", BLSPubkey),
]

class ExecutionPayloadHeader(HashableContainer):
# Execution block header fields
fields = [
("parent_hash", Hash32),
("fee_recipient", ExecutionAddress),
("state_root", bytes32),
("receipts_root", bytes32),
("logs_bloom", ByteVector(constants.BYTES_PER_LOGS_BLOOM)),
("prev_randao", bytes32),
("block_number", uint64),
("gas_limit", uint64),
("gas_used", uint64),
("timestamp", uint64),
# ("extra_data", ByteList(constants.MAX_EXTRA_DATA_BYTES)),
# workaround - looks like ByteList is partially broken, but extra data is exactly bytes32
("extra_data", List(byte, constants.MAX_EXTRA_DATA_BYTES)),
("base_fee_per_gas", uint256),
("block_hash", Hash32),
("transactions_root", Root),
("withdrawals_root", Root),
# ("excess_data_gas: uint256", uint256),
]

class HistoricalSummary(HashableContainer):
"""
`HistoricalSummary` matches the components of the phase0 `HistoricalBatch`
making the two hash_tree_root-compatible.
"""
fields = [
("block_summary_root", Root),
("state_summary_root", Root),
]


Balances = List(Gwei, constants.VALIDATOR_REGISTRY_LIMIT)


class BeaconState:
fields = [
# Versioning
("genesis_time", uint64),
("genesis_validators_root", Root),
("slot", Slot),
("fork", Fork),
# History
("latest_block_header", BeaconBlockHeader),
("block_roots", Vector(Root, constants.SLOTS_PER_HISTORICAL_ROOT)),
("state_roots", Vector(Root, constants.SLOTS_PER_HISTORICAL_ROOT)),
("historical_roots", List(Root, constants.HISTORICAL_ROOTS_LIMIT)), # Frozen in Capella, replaced by historical_summaries
# Eth1
("eth1_data", Eth1Data),
("eth1_data_votes", List(Eth1Data, constants.EPOCHS_PER_ETH1_VOTING_PERIOD * constants.SLOTS_PER_EPOCH)),
("eth1_deposit_index", uint64),
# Registry
("validators", List(Validator, constants.VALIDATOR_REGISTRY_LIMIT)),
("balances", Balances),
# Randomness
("randao_mixes", Vector(bytes32, constants.EPOCHS_PER_HISTORICAL_VECTOR)),
# Slashings
("slashings", Vector(Gwei, constants.EPOCHS_PER_SLASHINGS_VECTOR)), # Per-epoch sums of slashed effective balances
# Participation
("previous_epoch_participation", List(ParticipationFlags, constants.VALIDATOR_REGISTRY_LIMIT)),
("current_epoch_participation", List(ParticipationFlags, constants.VALIDATOR_REGISTRY_LIMIT)),
# Finality
("justification_bits", Bitvector(constants.JUSTIFICATION_BITS_LENGTH)), # Bit set for every recent justified epoch
("previous_justified_checkpoint", Checkpoint),
("current_justified_checkpoint", Checkpoint),
("finalized_checkpoint", Checkpoint),
# Inactivity
("inactivity_scores", List(uint64, constants.VALIDATOR_REGISTRY_LIMIT)),
# Sync
("current_sync_committee", SyncCommittee),
("next_sync_committee", SyncCommittee),
# Execution
("latest_execution_payload_header", ExecutionPayloadHeader), # (Modified in Capella)
# Withdrawals
("next_withdrawal_index", WithdrawalIndex), # (New in Capella)
("next_withdrawal_validator_index", ValidatorIndex), # (New in Capella)
# Deep history valid from Capella onwards
("historical_summaries", List(HistoricalSummary, constants.HISTORICAL_ROOTS_LIMIT)), # (New in Capella)
]
54 changes: 54 additions & 0 deletions storage-proof-app/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#misc
from enum import Enum

FAR_FUTURE_EPOCH = 2 ** 64 - 1
JUSTIFICATION_BITS_LENGTH = 4
MAX_VALIDATORS_PER_COMMITTEE = 2 ** 11
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#time-parameters-1
MIN_VALIDATOR_WITHDRAWABILITY_DELAY = 2**8
SHARD_COMMITTEE_PERIOD = 256
MIN_ATTESTATION_INCLUSION_DELAY = 2**0
SLOTS_PER_EPOCH = 2**5
MIN_SEED_LOOKAHEAD = 2**0
MAX_SEED_LOOKAHEAD = 2**2
MIN_EPOCHS_TO_INACTIVITY_PENALTY = 2**2
EPOCHS_PER_ETH1_VOTING_PERIOD = 2**6
SLOTS_PER_HISTORICAL_ROOT = 2**13
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#state-list-lengths
EPOCHS_PER_HISTORICAL_VECTOR = 2**16
EPOCHS_PER_SLASHINGS_VECTOR = 2**13
HISTORICAL_ROOTS_LIMIT = 2**24
VALIDATOR_REGISTRY_LIMIT = 2**40
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#rewards-and-penalties
PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX = 3
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#gwei-values
EFFECTIVE_BALANCE_INCREMENT = 2 ** 0 * 10 ** 9
MAX_EFFECTIVE_BALANCE = 32 * 10 ** 9
# https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#execution
MAX_WITHDRAWALS_PER_PAYLOAD = 2 ** 4
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#withdrawal-prefixes
ETH1_ADDRESS_WITHDRAWAL_PREFIX = '0x01'
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#validator-cycle
MIN_PER_EPOCH_CHURN_LIMIT = 2 ** 2
CHURN_LIMIT_QUOTIENT = 2 ** 16
# https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#max-operations-per-block
MAX_ATTESTATIONS = 2 ** 7

# https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#sync-committee
SYNC_COMMITTEE_SIZE = 2 ** 9
BYTES_PER_LOGS_BLOOM = 2 ** 8
MAX_EXTRA_DATA_BYTES = 2 ** 5


# Local constants
GWEI_TO_WEI = 10 ** 9
SHARE_RATE_PRECISION_E27 = 10**27
TOTAL_BASIS_POINTS = 10000

MAX_BLOCK_GAS_LIMIT = 30_000_000


class Chain(Enum):
MAINNET = "mainnet"
GOERLI = "goerli"
SEPOLIA = "sepolia"
15 changes: 15 additions & 0 deletions storage-proof-app/mock_beacon_state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from pymerkle import InmemoryTree as MerkleTree
from consensus_layer_ssz import BeaconState
import random

def get_random_state():
state_size = 16
state_data = [random.randint(0, (1<<16)) for _ in range(state_size)]
tree = MerkleTree()
for x in state_data:
tree.append_entry(bytes(x))
root = tree.get_state()

state: BeaconState = BeaconState()
state.state_root = [root]
return state, tree
22 changes: 22 additions & 0 deletions storage-proof-app/mock_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from mock_beacon_state import get_random_state
import json
import sys
import random

def slice_into_low_high(digest):
low = int.from_bytes(digest[:16], 'big')
high = int.from_bytes(digest[16:], 'big')

return [low, high]

if __name__ == "__main__":
index = random.randint(1, 16)
state, tree = get_random_state()
merkle_path = tree.prove_inclusion(index)
leaf = tree.get_leaf(index)
root = state.state_root[0]
print(f"Preparing Merkle path for index {index - 1} and state root {root.hex()}")
input = [{"array" : [slice_into_low_high(node) for node in merkle_path.path]}, {"vector": slice_into_low_high(leaf)}, {"vector" : slice_into_low_high(root)}]
with open("path.inp", 'w') as f:
sys.stdout = f
print(json.dumps(input, indent=4))
2 changes: 2 additions & 0 deletions storage-proof-app/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pymerkle==6.0.0
ssz==0.3.1

0 comments on commit e1f5265

Please sign in to comment.