Merge pull request #2082 from ethereum/v1.0-candidate

v1.0 release
This commit is contained in:
Danny Ryan 2020-11-04 09:40:59 -06:00 committed by GitHub
commit 579da6d2dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 801 additions and 204 deletions

View File

@ -1,6 +1,4 @@
# Mainnet preset # Mainnet preset
# Note: the intention of this file (for now) is to illustrate what a mainnet configuration could look like.
# Some of these constants may still change before the launch of Phase 0.
CONFIG_NAME: "mainnet" CONFIG_NAME: "mainnet"
@ -20,16 +18,14 @@ CHURN_LIMIT_QUOTIENT: 65536
SHUFFLE_ROUND_COUNT: 90 SHUFFLE_ROUND_COUNT: 90
# `2**14` (= 16,384) # `2**14` (= 16,384)
MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384 MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384
# Jan 3, 2020 # Dec 1, 2020, 12pm UTC
MIN_GENESIS_TIME: 1578009600 MIN_GENESIS_TIME: 1606824000
# 4 # 4
HYSTERESIS_QUOTIENT: 4 HYSTERESIS_QUOTIENT: 4
# 1 (minus 0.25) # 1 (minus 0.25)
HYSTERESIS_DOWNWARD_MULTIPLIER: 1 HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25) # 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5 HYSTERESIS_UPWARD_MULTIPLIER: 5
# 3
PROPORTIONAL_SLASHING_MULTIPLIER: 3
# Fork Choice # Fork Choice
@ -40,8 +36,8 @@ SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 8
# Validator # Validator
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 2**10 (= 1,024) # 2**11 (= 2,048)
ETH1_FOLLOW_DISTANCE: 1024 ETH1_FOLLOW_DISTANCE: 2048
# 2**4 (= 16) # 2**4 (= 16)
TARGET_AGGREGATORS_PER_COMMITTEE: 16 TARGET_AGGREGATORS_PER_COMMITTEE: 16
# 2**0 (= 1) # 2**0 (= 1)
@ -58,7 +54,7 @@ SECONDS_PER_ETH1_BLOCK: 14
DEPOSIT_CHAIN_ID: 1 DEPOSIT_CHAIN_ID: 1
DEPOSIT_NETWORK_ID: 1 DEPOSIT_NETWORK_ID: 1
# **TBD** # **TBD**
DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa
# Gwei values # Gwei values
@ -82,8 +78,8 @@ BLS_WITHDRAWAL_PREFIX: 0x00
# Time parameters # Time parameters
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 172800 seconds (2 days) # 604800 seconds (7 days)
GENESIS_DELAY: 172800 GENESIS_DELAY: 604800
# 12 seconds # 12 seconds
SECONDS_PER_SLOT: 12 SECONDS_PER_SLOT: 12
# 2**0 (= 1) slots 12 seconds # 2**0 (= 1) slots 12 seconds
@ -94,8 +90,8 @@ SLOTS_PER_EPOCH: 32
MIN_SEED_LOOKAHEAD: 1 MIN_SEED_LOOKAHEAD: 1
# 2**2 (= 4) epochs 25.6 minutes # 2**2 (= 4) epochs 25.6 minutes
MAX_SEED_LOOKAHEAD: 4 MAX_SEED_LOOKAHEAD: 4
# 2**5 (= 32) epochs ~3.4 hours # 2**6 (= 64) epochs ~6.8 hours
EPOCHS_PER_ETH1_VOTING_PERIOD: 32 EPOCHS_PER_ETH1_VOTING_PERIOD: 64
# 2**13 (= 8,192) slots ~13 hours # 2**13 (= 8,192) slots ~13 hours
SLOTS_PER_HISTORICAL_ROOT: 8192 SLOTS_PER_HISTORICAL_ROOT: 8192
# 2**8 (= 256) epochs ~27 hours # 2**8 (= 256) epochs ~27 hours
@ -126,10 +122,12 @@ BASE_REWARD_FACTOR: 64
WHISTLEBLOWER_REWARD_QUOTIENT: 512 WHISTLEBLOWER_REWARD_QUOTIENT: 512
# 2**3 (= 8) # 2**3 (= 8)
PROPOSER_REWARD_QUOTIENT: 8 PROPOSER_REWARD_QUOTIENT: 8
# 2**24 (= 16,777,216) # 2**26 (= 67,108,864)
INACTIVITY_PENALTY_QUOTIENT: 16777216 INACTIVITY_PENALTY_QUOTIENT: 67108864
# 2**5 (= 32) # 2**7 (= 128) (lower safety margin at Phase 0 genesis)
MIN_SLASHING_PENALTY_QUOTIENT: 32 MIN_SLASHING_PENALTY_QUOTIENT: 128
# 1 (lower safety margin at Phase 0 genesis)
PROPORTIONAL_SLASHING_MULTIPLIER: 1
# Max operations per block # Max operations per block

View File

@ -27,8 +27,6 @@ HYSTERESIS_QUOTIENT: 4
HYSTERESIS_DOWNWARD_MULTIPLIER: 1 HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25) # 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5 HYSTERESIS_UPWARD_MULTIPLIER: 5
# 3
PROPORTIONAL_SLASHING_MULTIPLIER: 3
# Fork Choice # Fork Choice
@ -56,7 +54,7 @@ SECONDS_PER_ETH1_BLOCK: 14
# Ethereum Goerli testnet # Ethereum Goerli testnet
DEPOSIT_CHAIN_ID: 5 DEPOSIT_CHAIN_ID: 5
DEPOSIT_NETWORK_ID: 5 DEPOSIT_NETWORK_ID: 5
# **TBD** # Configured on a per testnet basis
DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890
@ -125,10 +123,12 @@ BASE_REWARD_FACTOR: 64
WHISTLEBLOWER_REWARD_QUOTIENT: 512 WHISTLEBLOWER_REWARD_QUOTIENT: 512
# 2**3 (= 8) # 2**3 (= 8)
PROPOSER_REWARD_QUOTIENT: 8 PROPOSER_REWARD_QUOTIENT: 8
# 2**24 (= 16,777,216) # [customized] 2**25 (= 33,554,432)
INACTIVITY_PENALTY_QUOTIENT: 16777216 INACTIVITY_PENALTY_QUOTIENT: 33554432
# 2**5 (= 32) # [customized] 2**6 (= 64)
MIN_SLASHING_PENALTY_QUOTIENT: 32 MIN_SLASHING_PENALTY_QUOTIENT: 64
# [customized] 2 (lower safety margin than Phase 0 genesis but different than mainnet config for testing)
PROPORTIONAL_SLASHING_MULTIPLIER: 2
# Max operations per block # Max operations per block

View File

@ -536,8 +536,8 @@ setup(
"eth-utils>=1.3.0,<2", "eth-utils>=1.3.0,<2",
"eth-typing>=2.1.0,<3.0.0", "eth-typing>=2.1.0,<3.0.0",
"pycryptodome==3.9.4", "pycryptodome==3.9.4",
"py_ecc==4.0.0", "py_ecc==5.0.0",
"milagro_bls_binding==1.3.0", "milagro_bls_binding==1.5.0",
"dataclasses==0.6", "dataclasses==0.6",
"remerkleable==0.1.17", "remerkleable==0.1.17",
"ruamel.yaml==0.16.5", "ruamel.yaml==0.16.5",

View File

@ -1,7 +1,5 @@
# Ethereum 2.0 Phase 0 -- The Beacon Chain # Ethereum 2.0 Phase 0 -- The Beacon Chain
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents ## Table of contents
<!-- TOC --> <!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
@ -177,13 +175,13 @@ The following values are (non-configurable) constants used throughout the specif
## Configuration ## Configuration
*Note*: The default mainnet configuration values are included here for spec-design purposes. The different configurations for mainnet, testnets, and YAML-based testing can be found in the [`configs/constant_presets`](../../configs) directory. These configurations are updated for releases and may be out of sync during `dev` changes. *Note*: The default mainnet configuration values are included here for illustrative purposes. The different configurations for mainnet, testnets, and YAML-based testing can be found in the [`configs/constant_presets`](../../configs) directory.
### Misc ### Misc
| Name | Value | | Name | Value |
| - | - | | - | - |
| `ETH1_FOLLOW_DISTANCE` | `uint64(2**10)` (= 1,024) | | `ETH1_FOLLOW_DISTANCE` | `uint64(2**11)` (= 2,048) |
| `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) | | `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) |
| `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) | | `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) |
| `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) | | `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) |
@ -191,11 +189,10 @@ The following values are (non-configurable) constants used throughout the specif
| `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) | | `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) |
| `SHUFFLE_ROUND_COUNT` | `uint64(90)` | | `SHUFFLE_ROUND_COUNT` | `uint64(90)` |
| `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) | | `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) |
| `MIN_GENESIS_TIME` | `uint64(1578009600)` (Jan 3, 2020) | | `MIN_GENESIS_TIME` | `uint64(1606824000)` (Dec 1, 2020, 12pm UTC) |
| `HYSTERESIS_QUOTIENT` | `uint64(4)` | | `HYSTERESIS_QUOTIENT` | `uint64(4)` |
| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` | | `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` |
| `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` | | `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` |
| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(3)` |
- For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.) - For the safety of committees, `TARGET_COMMITTEE_SIZE` exceeds [the recommended minimum committee size of 111](http://web.archive.org/web/20190504131341/https://vitalik.ca/files/Ithaca201807_Sharding.pdf); with sufficient active validators (at least `SLOTS_PER_EPOCH * TARGET_COMMITTEE_SIZE`), the shuffling algorithm ensures committee sizes of at least `TARGET_COMMITTEE_SIZE`. (Unbiasable randomness with a Verifiable Delay Function (VDF) will improve committee robustness and lower the safe minimum committee size.)
@ -219,7 +216,7 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value | Unit | Duration | | Name | Value | Unit | Duration |
| - | - | :-: | :-: | | - | - | :-: | :-: |
| `GENESIS_DELAY` | `uint64(172800)` | seconds | 2 days | | `GENESIS_DELAY` | `uint64(604800)` | seconds | 7 days |
| `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds | | `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds |
| `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds | | `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds |
| `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds |
@ -227,7 +224,7 @@ The following values are (non-configurable) constants used throughout the specif
| `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes | | `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes |
| `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | | `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes |
| `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | | `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes |
| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**5)` (= 32) | epochs | ~3.4 hours | | `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**6)` (= 64) | epochs | ~6.8 hours |
| `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours | | `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours |
| `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
| `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | | `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours |
@ -248,10 +245,13 @@ The following values are (non-configurable) constants used throughout the specif
| `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) | | `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) |
| `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) | | `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) |
| `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) | | `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) |
| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**24)` (= 16,777,216) | | `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**26)` (= 67,108,864) |
| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**5)` (= 32) | | `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**7)` (=128) |
| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(1)` |
- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12` epochs (about 18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. - The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**13` epochs (about 36 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. Note this value will be upgraded to `2**24` after Phase 0 mainnet stabilizes to provide a faster recovery in the event of an inactivity leak.
- The `PROPORTIONAL_SLASHING_MULTIPLIER` is set to `1` at initial mainnet launch, resulting in one-third of the minimum accountable safety margin in the event of a finality attack. After Phase 0 mainnet stablizes, this value will be upgraded to `3` to provide the maximal minimum accoutable safety margin.
### Max operations per block ### Max operations per block
@ -603,7 +603,7 @@ def bytes_to_uint64(data: bytes) -> uint64:
#### BLS Signatures #### BLS Signatures
Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-03](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03). Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` ciphersuite which implements the following interfaces: Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04). Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` ciphersuite which implements the following interfaces:
- `def Sign(SK: int, message: Bytes) -> BLSSignature` - `def Sign(SK: int, message: Bytes) -> BLSSignature`
- `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool` - `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool`
@ -1189,8 +1189,6 @@ def is_valid_genesis_state(state: BeaconState) -> bool:
return True return True
``` ```
*Note*: The `is_valid_genesis_state` function (including `MIN_GENESIS_TIME` and `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT`) is a placeholder for testing. It has yet to be finalized by the community, and can be updated as necessary.
### Genesis block ### Genesis block
Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`.
@ -1200,7 +1198,7 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`.
The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid. The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid.
```python ```python
def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> BeaconState: def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None:
block = signed_block.message block = signed_block.message
# Process slots (including those with no blocks) since block # Process slots (including those with no blocks) since block
process_slots(state, block.slot) process_slots(state, block.slot)
@ -1212,8 +1210,6 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida
# Verify state root # Verify state root
if validate_result: if validate_result:
assert block.state_root == hash_tree_root(state) assert block.state_root == hash_tree_root(state)
# Return post-state
return state
``` ```
```python ```python

View File

@ -1,7 +1,5 @@
# Ethereum 2.0 Phase 0 -- Deposit Contract # Ethereum 2.0 Phase 0 -- Deposit Contract
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents ## Table of contents
<!-- TOC --> <!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
@ -43,7 +41,7 @@ These configurations are updated for releases and may be out of sync during `dev
| - | - | | - | - |
| `DEPOSIT_CHAIN_ID` | `1` | | `DEPOSIT_CHAIN_ID` | `1` |
| `DEPOSIT_NETWORK_ID` | `1` | | `DEPOSIT_NETWORK_ID` | `1` |
| `DEPOSIT_CONTRACT_ADDRESS` | **TBD** | | `DEPOSIT_CONTRACT_ADDRESS` | `0x00000000219ab540356cBB839Cbe05303d7705Fa` |
## Ethereum 1.0 deposit contract ## Ethereum 1.0 deposit contract

View File

@ -1,7 +1,5 @@
# Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice # Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents ## Table of contents
<!-- TOC --> <!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
@ -226,11 +224,10 @@ def get_head(store: Store) -> Root:
blocks = get_filtered_block_tree(store) blocks = get_filtered_block_tree(store)
# Execute the LMD-GHOST fork choice # Execute the LMD-GHOST fork choice
head = store.justified_checkpoint.root head = store.justified_checkpoint.root
justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch)
while True: while True:
children = [ children = [
root for root in blocks.keys() root for root in blocks.keys()
if blocks[root].parent_root == head and blocks[root].slot > justified_slot if blocks[root].parent_root == head
] ]
if len(children) == 0: if len(children) == 0:
return head return head
@ -355,7 +352,8 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root
# Check the block is valid and compute the post-state # Check the block is valid and compute the post-state
state = state_transition(pre_state, signed_block, True) state = pre_state.copy()
state_transition(state, signed_block, True)
# Add new block to the store # Add new block to the store
store.blocks[hash_tree_root(block)] = block store.blocks[hash_tree_root(block)] = block
# Add new state for this block to the store # Add new state for this block to the store

View File

@ -54,8 +54,6 @@ It consists of four main sections:
- [ENR structure](#enr-structure) - [ENR structure](#enr-structure)
- [Attestation subnet bitfield](#attestation-subnet-bitfield) - [Attestation subnet bitfield](#attestation-subnet-bitfield)
- [`eth2` field](#eth2-field) - [`eth2` field](#eth2-field)
- [General capabilities](#general-capabilities)
- [Topic advertisement](#topic-advertisement)
- [Design decision rationale](#design-decision-rationale) - [Design decision rationale](#design-decision-rationale)
- [Transport](#transport-1) - [Transport](#transport-1)
- [Why are we defining specific transports?](#why-are-we-defining-specific-transports) - [Why are we defining specific transports?](#why-are-we-defining-specific-transports)
@ -78,6 +76,7 @@ It consists of four main sections:
- [How do we upgrade gossip channels (e.g. changes in encoding, compression)?](#how-do-we-upgrade-gossip-channels-eg-changes-in-encoding-compression) - [How do we upgrade gossip channels (e.g. changes in encoding, compression)?](#how-do-we-upgrade-gossip-channels-eg-changes-in-encoding-compression)
- [Why must all clients use the same gossip topic instead of one negotiated between each peer pair?](#why-must-all-clients-use-the-same-gossip-topic-instead-of-one-negotiated-between-each-peer-pair) - [Why must all clients use the same gossip topic instead of one negotiated between each peer pair?](#why-must-all-clients-use-the-same-gossip-topic-instead-of-one-negotiated-between-each-peer-pair)
- [Why are the topics strings and not hashes?](#why-are-the-topics-strings-and-not-hashes) - [Why are the topics strings and not hashes?](#why-are-the-topics-strings-and-not-hashes)
- [Why are we using the `StrictNoSign` signature policy?](#why-are-we-using-the-strictnosign-signature-policy)
- [Why are we overriding the default libp2p pubsub `message-id`?](#why-are-we-overriding-the-default-libp2p-pubsub-message-id) - [Why are we overriding the default libp2p pubsub `message-id`?](#why-are-we-overriding-the-default-libp2p-pubsub-message-id)
- [Why are these specific gossip parameters chosen?](#why-are-these-specific-gossip-parameters-chosen) - [Why are these specific gossip parameters chosen?](#why-are-these-specific-gossip-parameters-chosen)
- [Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?](#why-is-there-maximum_gossip_clock_disparity-when-validating-slot-ranges-of-messages-in-gossip-subnets) - [Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?](#why-is-there-maximum_gossip_clock_disparity-when-validating-slot-ranges-of-messages-in-gossip-subnets)
@ -178,6 +177,9 @@ This section outlines constants that are used in this spec.
| `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. |
| `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. | | `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. |
| `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500ms` | The maximum milliseconds of clock disparity assumed between honest nodes. | | `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500ms` | The maximum milliseconds of clock disparity assumed between honest nodes. |
| `MESSAGE_DOMAIN_INVALID_SNAPPY` | `0x00000000` | 4-byte domain for gossip message-id isolation of *invalid* snappy messages |
| `MESSAGE_DOMAIN_VALID_SNAPPY` | `0x01000000` | 4-byte domain for gossip message-id isolation of *valid* snappy messages |
## MetaData ## MetaData
@ -209,12 +211,10 @@ including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsu
**Gossipsub Parameters** **Gossipsub Parameters**
*Note*: Parameters listed here are subject to a large-scale network feasibility study.
The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) will be used: The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) will be used:
- `D` (topic stable mesh target count): 6 - `D` (topic stable mesh target count): 8
- `D_low` (topic stable mesh low watermark): 5 - `D_low` (topic stable mesh low watermark): 6
- `D_high` (topic stable mesh high watermark): 12 - `D_high` (topic stable mesh high watermark): 12
- `D_lazy` (gossip target): 6 - `D_lazy` (gossip target): 6
- `heartbeat_interval` (frequency of heartbeat, seconds): 0.7 - `heartbeat_interval` (frequency of heartbeat, seconds): 0.7
@ -223,6 +223,11 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master
- `mcache_gossip` (number of windows to gossip about): 3 - `mcache_gossip` (number of windows to gossip about): 3
- `seen_ttl` (number of heartbeat intervals to retain message IDs): 550 - `seen_ttl` (number of heartbeat intervals to retain message IDs): 550
*Note*: Gossipsub v1.1 introduces a number of
[additional parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#overview-of-new-parameters)
for peer scoring and other attack mitigations.
These are currently under investigation and will be spec'd and released to mainnet when they are ready.
### Topics and messages ### Topics and messages
Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages).
@ -243,11 +248,22 @@ Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/
Clients MUST reject (fail validation) messages that are over this size limit. Clients MUST reject (fail validation) messages that are over this size limit.
Likewise, clients MUST NOT emit or propagate messages larger than this limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit.
The `message-id` of a gossipsub message MUST be the first 8 bytes of the SHA-256 hash of the message data, i.e.: The optional `from` (1), `seqno` (3), `signature` (5) and `key` (6) protobuf fields are omitted from the message,
since messages are identified by content, anonymous, and signed where necessary in the application layer.
Starting from Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign`
[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options).
```python The `message-id` of a gossipsub message MUST be the following 20 byte value computed from the message data:
message-id: SHA256(message.data)[0:8] * If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of
``` the concatenation of `MESSAGE_DOMAIN_VALID_SNAPPY` with the snappy decompressed message data,
i.e. `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]`.
* Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of
the concatenation of `MESSAGE_DOMAIN_INVALID_SNAPPY` with the raw message data,
i.e. `SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + message.data)[:20]`.
*Note*: The above logic handles two exceptional cases:
(1) multiple snappy `data` can decompress to the same value,
and (2) some message `data` can fail to snappy decompress altogether.
The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic: The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic:
@ -317,6 +333,8 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_
- _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- - _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) --
i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot`
(a client MAY queue future aggregates for processing at the appropriate slot). (a client MAY queue future aggregates for processing at the appropriate slot).
- _[REJECT]_ The aggregate attestation's epoch matches its target -- i.e. `aggregate.data.target.epoch ==
compute_epoch_at_slot(aggregate.data.slot)`
- _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen - _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen
(via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally).
- _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator - _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator
@ -734,7 +752,7 @@ and MUST support serving requests of blocks up to their own `head_block_root`.
Clients MUST respond with at least the first block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOCKS` blocks. Clients MUST respond with at least the first block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOCKS` blocks.
The following blocks, where they exist, MUST be send in consecutive order. The following blocks, where they exist, MUST be sent in consecutive order.
Clients MAY limit the number of blocks in the response. Clients MAY limit the number of blocks in the response.
@ -846,13 +864,11 @@ The response MUST consist of a single `response_chunk`.
## The discovery domain: discv5 ## The discovery domain: discv5
Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) is used for peer discovery. Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) (Protocol version v5.1) is used for peer discovery.
`discv5` is a standalone protocol, running on UDP on a dedicated port, meant for peer discovery only. `discv5` is a standalone protocol, running on UDP on a dedicated port, meant for peer discovery only.
`discv5` supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are (or will be) requirements in this context. `discv5` supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are (or will be) requirements in this context.
:warning: Under construction. :warning:
### Integration into libp2p stacks ### Integration into libp2p stacks
`discv5` SHOULD be integrated into the clients libp2p stack by implementing an adaptor `discv5` SHOULD be integrated into the clients libp2p stack by implementing an adaptor
@ -935,19 +951,6 @@ Clients MAY connect to peers with the same `fork_digest` but a different `next_f
Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients,
these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`.
#### General capabilities
ENRs MUST include a structure enumerating the capabilities offered by the peer in an efficient manner.
The concrete solution is currently undefined.
Proposals include using namespaced bloom filters mapping capabilities to specific protocol IDs supported under that capability.
### Topic advertisement
discv5's topic advertisement feature is not expected to be ready for mainnet launch of Phase 0.
Once this feature is built out and stable, we expect to use topic advertisement as a rendezvous facility for peers on shards.
Until then, the ENR [attestation subnet bitfield](#attestation-subnet-bitfield) will be used for discovery of peers on particular subnets.
# Design decision rationale # Design decision rationale
## Transport ## Transport
@ -1164,10 +1167,16 @@ since the domain is finite anyway, and calculating a digest's preimage would be
Furthermore, the Eth2 topic names are shorter than their digest equivalents (assuming SHA-256 hash), Furthermore, the Eth2 topic names are shorter than their digest equivalents (assuming SHA-256 hash),
so hashing topics would bloat messages unnecessarily. so hashing topics would bloat messages unnecessarily.
### Why are we using the `StrictNoSign` signature policy?
The policy omits the `from` (1), `seqno` (3), `signature` (5) and `key` (6) fields. These fields would:
- Expose origin of sender (`from`), type of sender (based on `seqno`)
- Add extra unused data to the gossip, since message IDs are based on `data`, not on the `from` and `seqno`.
- Introduce more message validation than necessary, e.g. no `signature`.
### Why are we overriding the default libp2p pubsub `message-id`? ### Why are we overriding the default libp2p pubsub `message-id`?
For our current purposes, there is no need to address messages based on source peer, For our current purposes, there is no need to address messages based on source peer, or track a message `seqno`.
and it seems likely we might even override the message `from` to obfuscate the peer.
By overriding the default `message-id` to use content-addressing we can filter unnecessary duplicates before hitting the application layer. By overriding the default `message-id` to use content-addressing we can filter unnecessary duplicates before hitting the application layer.
Some examples of where messages could be duplicated: Some examples of where messages could be duplicated:
@ -1210,8 +1219,6 @@ For minimum and maximum allowable slot broadcast times,
Although messages can at times be eagerly gossiped to the network, Although messages can at times be eagerly gossiped to the network,
the node's fork choice prevents integration of these messages into the actual consensus until the _actual local start_ of the designated slot. the node's fork choice prevents integration of these messages into the actual consensus until the _actual local start_ of the designated slot.
The value of this constant is currently a placeholder and will be tuned based on data observed in testnets.
### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets? ### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets?
Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel. Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel.
@ -1408,7 +1415,7 @@ discv5 supports self-certified, flexible peer records (ENRs) and topic-based adv
On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implementations On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implementations
with content routing and storage capabilities, both of which are irrelevant in this context. with content routing and storage capabilities, both of which are irrelevant in this context.
We assume that Eth 1.0 nodes will evolve to support discv5. Eth 1.0 nodes will evolve to support discv5.
By sharing the discovery network between Eth 1.0 and 2.0, By sharing the discovery network between Eth 1.0 and 2.0,
we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks, we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks,
to which smaller networks are more vulnerable. to which smaller networks are more vulnerable.
@ -1444,7 +1451,7 @@ ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cl
so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain.
Once genesis data is known, we can then form ENRs and safely find peers. Once genesis data is known, we can then form ENRs and safely find peers.
When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (48hours in mainnet configuration) before `genesis_time`, When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (7 days in mainnet configuration) before `genesis_time`,
providing ample time to find peers and form initial connections and gossip subnets prior to genesis. providing ample time to find peers and form initial connections and gossip subnets prior to genesis.
## Compression/Encoding ## Compression/Encoding

View File

@ -1,6 +1,6 @@
# Ethereum 2.0 Phase 0 -- Honest Validator # Ethereum 2.0 Phase 0 -- Honest Validator
**Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol.
## Table of contents ## Table of contents
@ -65,6 +65,7 @@
- [How to avoid slashing](#how-to-avoid-slashing) - [How to avoid slashing](#how-to-avoid-slashing)
- [Proposer slashing](#proposer-slashing) - [Proposer slashing](#proposer-slashing)
- [Attester slashing](#attester-slashing) - [Attester slashing](#attester-slashing)
- [Protection best practices](#protection-best-practices)
<!-- END doctoc generated TOC please keep comment here to allow auto update --> <!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC --> <!-- /TOC -->
@ -129,7 +130,7 @@ To submit a deposit:
### Process deposit ### Process deposit
Deposits cannot be processed into the beacon chain until the Eth1 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~4 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~3.4 hours). Once the requisite Eth1 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated. Deposits cannot be processed into the beacon chain until the Eth1 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~8 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~6.8 hours). Once the requisite Eth1 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated.
### Validator index ### Validator index
@ -289,7 +290,7 @@ class Eth1Block(Container):
Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block. Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block.
An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state)` where: An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state, eth1_chain)` where:
```python ```python
def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64:
@ -358,6 +359,10 @@ The `proof` for each deposit must be constructed against the deposit root contai
Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](./beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](./beacon-chain.md#voluntary-exits). Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](./beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](./beacon-chain.md#voluntary-exits).
*Note*: If a slashing for a validator is included in the same block as a
voluntary exit, the voluntary exit will fail and cause the block to be invalid
due to the slashing being processed first. Implementers must take heed of this
operation interaction when packing blocks.
#### Packaging into a `SignedBeaconBlock` #### Packaging into a `SignedBeaconBlock`
@ -372,7 +377,7 @@ It is useful to be able to run a state transition function (working on a copy of
def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root:
temp_state: BeaconState = state.copy() temp_state: BeaconState = state.copy()
signed_block = SignedBeaconBlock(message=block) signed_block = SignedBeaconBlock(message=block)
temp_state = state_transition(temp_state, signed_block, validate_result=False) state_transition(temp_state, signed_block, validate_result=False)
return hash_tree_root(temp_state) return hash_tree_root(temp_state)
``` ```
@ -604,3 +609,13 @@ Specifically, when signing an `Attestation`, a validator should perform the foll
2. Generate and broadcast attestation. 2. Generate and broadcast attestation.
If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing. If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing.
## Protection best practices
A validator client should be considered standalone and should consider the beacon node as untrusted. This means that the validator client should protect:
1) Private keys -- private keys should be protected from being exported accidentally or by an attacker.
2) Slashing -- before a validator client signs a message it should validate the data, check it against a local slashing database (do not sign a slashable attestation or block) and update its internal slashing database with the newly signed object.
3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. See [EIP 3076](https://github.com/ethereum/EIPs/pull/3076/files) for a standard slashing interchange format.
4) Far future signing requests -- A validator client can be requested to sign a far into the future attestation, resulting in a valid non-slashable request. If the validator client signs this message, it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity leak and potential ejection due to low balance.
A validator client should prevent itself from signing such requests by: a) keeping a local time clock if possible and following best practices to stop time server attacks and b) refusing to sign, by default, any message that has a large (>6h) gap from the current slashing protection database indicated a time "jump" or a long offline event. The administrator can manually override this protection to restart the validator after a genuine long offline event.

View File

@ -1,7 +1,5 @@
# Ethereum 2.0 Phase 0 -- Weak Subjectivity Guide # Ethereum 2.0 Phase 0 -- Weak Subjectivity Guide
**Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents ## Table of contents
<!-- TOC --> <!-- TOC -->
@ -69,9 +67,9 @@ def compute_weak_subjectivity_period(state: BeaconState) -> uint64:
weak_subjectivity_period = MIN_VALIDATOR_WITHDRAWABILITY_DELAY weak_subjectivity_period = MIN_VALIDATOR_WITHDRAWABILITY_DELAY
validator_count = len(get_active_validator_indices(state, get_current_epoch(state))) validator_count = len(get_active_validator_indices(state, get_current_epoch(state)))
if validator_count >= MIN_PER_EPOCH_CHURN_LIMIT * CHURN_LIMIT_QUOTIENT: if validator_count >= MIN_PER_EPOCH_CHURN_LIMIT * CHURN_LIMIT_QUOTIENT:
weak_subjectivity_period += SAFETY_DECAY * CHURN_LIMIT_QUOTIENT / (2 * 100) weak_subjectivity_period += SAFETY_DECAY * CHURN_LIMIT_QUOTIENT // (2 * 100)
else: else:
weak_subjectivity_period += SAFETY_DECAY * validator_count / (2 * 100 * MIN_PER_EPOCH_CHURN_LIMIT) weak_subjectivity_period += SAFETY_DECAY * validator_count // (2 * 100 * MIN_PER_EPOCH_CHURN_LIMIT)
return weak_subjectivity_period return weak_subjectivity_period
``` ```

View File

@ -6,7 +6,7 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents**
- [Introduction](#introduction) - [Introduction](#introduction)
- [Custom types](#custom-types) - [Custom types](#custom-types)

View File

@ -1,28 +1,20 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1](#ethereum-20-phase-1----from-phase-0-to-phase-1)
- [Table of contents](#table-of-contents)
- [Introduction](#introduction)
- [Configuration](#configuration)
- [Fork to Phase 1](#fork-to-phase-1)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1 # Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1
**Notice**: This document is a work-in-progress for researchers and implementers. **Notice**: This document is a work-in-progress for researchers and implementers.
## Table of contents ## Table of contents
<!-- TOC --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
TODO
<!-- /TOC --> - [Introduction](#introduction)
- [Configuration](#configuration)
- [Fork to Phase 1](#fork-to-phase-1)
- [Fork trigger](#fork-trigger)
- [Upgrading the state](#upgrading-the-state)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Introduction ## Introduction

View File

@ -6,7 +6,7 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Introduction](#introduction) - [Introduction](#introduction)
- [Helper functions](#helper-functions) - [Helper functions](#helper-functions)
@ -72,14 +72,13 @@ The post-state corresponding to a pre-state `shard_state` and a signed block `si
def shard_state_transition(shard_state: ShardState, def shard_state_transition(shard_state: ShardState,
signed_block: SignedShardBlock, signed_block: SignedShardBlock,
beacon_parent_state: BeaconState, beacon_parent_state: BeaconState,
validate_result: bool = True) -> ShardState: validate_result: bool = True) -> None:
assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message) assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message)
if validate_result: if validate_result:
assert verify_shard_block_signature(beacon_parent_state, signed_block) assert verify_shard_block_signature(beacon_parent_state, signed_block)
process_shard_block(shard_state, signed_block.message) process_shard_block(shard_state, signed_block.message)
return shard_state
``` ```
```python ```python

View File

@ -1,7 +1,5 @@
# SimpleSerialize (SSZ) # SimpleSerialize (SSZ)
**Notice**: This document is a work-in-progress describing typing, serialization, and Merkleization of Eth2 objects.
## Table of contents ## Table of contents
<!-- TOC --> <!-- TOC -->
<!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- START doctoc generated TOC please keep comment here to allow auto update -->
@ -252,7 +250,7 @@ We similarly define "summary types" and "expansion types". For example, [`Beacon
| Language | Project | Maintainer | Implementation | | Language | Project | Maintainer | Implementation |
|-|-|-|-| |-|-|-|-|
| Python | Ethereum 2.0 | Ethereum Foundation | [https://github.com/ethereum/py-ssz](https://github.com/ethereum/py-ssz) | | Python | Ethereum 2.0 | Ethereum Foundation | [https://github.com/ethereum/py-ssz](https://github.com/ethereum/py-ssz) |
| Rust | Lighthouse | Sigma Prime | [https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz](https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz) | | Rust | Lighthouse | Sigma Prime | [https://github.com/sigp/lighthouse/tree/master/consensus/ssz](https://github.com/sigp/lighthouse/tree/master/consensus/ssz) |
| Nim | Nimbus | Status | [https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) | | Nim | Nimbus | Status | [https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) |
| Rust | Shasper | ParityTech | [https://github.com/paritytech/shasper/tree/master/utils/ssz](https://github.com/paritytech/shasper/tree/master/utils/ssz) | | Rust | Shasper | ParityTech | [https://github.com/paritytech/shasper/tree/master/utils/ssz](https://github.com/paritytech/shasper/tree/master/utils/ssz) |
| TypeScript | Lodestar | ChainSafe Systems | [https://github.com/ChainSafe/ssz-js](https://github.com/ChainSafe/ssz) | | TypeScript | Lodestar | ChainSafe Systems | [https://github.com/ChainSafe/ssz-js](https://github.com/ChainSafe/ssz) |

View File

@ -1 +1 @@
0.12.3 1.0.0

View File

@ -1,8 +1,11 @@
from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation
def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False): def get_valid_attester_slashing(spec, state, slot=None, signed_1=False, signed_2=False, filter_participant_set=None):
attestation_1 = get_valid_attestation(spec, state, signed=signed_1) attestation_1 = get_valid_attestation(
spec, state,
slot=slot, signed=signed_1, filter_participant_set=filter_participant_set
)
attestation_2 = attestation_1.copy() attestation_2 = attestation_1.copy()
attestation_2.data.target.root = b'\x01' * 32 attestation_2.data.target.root = b'\x01' * 32
@ -16,14 +19,17 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False):
) )
def get_valid_attester_slashing_by_indices(spec, state, indices_1, indices_2=None, signed_1=False, signed_2=False): def get_valid_attester_slashing_by_indices(spec, state,
indices_1, indices_2=None,
slot=None,
signed_1=False, signed_2=False):
if indices_2 is None: if indices_2 is None:
indices_2 = indices_1 indices_2 = indices_1
assert indices_1 == sorted(indices_1) assert indices_1 == sorted(indices_1)
assert indices_2 == sorted(indices_2) assert indices_2 == sorted(indices_2)
attester_slashing = get_valid_attester_slashing(spec, state) attester_slashing = get_valid_attester_slashing(spec, state, slot=slot)
attester_slashing.attestation_1.attesting_indices = indices_1 attester_slashing.attestation_1.attesting_indices = indices_1
attester_slashing.attestation_2.attesting_indices = indices_2 attester_slashing.attestation_2.attesting_indices = indices_2

View File

@ -1,3 +1,5 @@
from random import Random
from eth2spec.test.helpers.keys import pubkeys, privkeys from eth2spec.test.helpers.keys import pubkeys, privkeys
from eth2spec.utils import bls from eth2spec.utils import bls
from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof
@ -54,7 +56,10 @@ def deposit_from_context(spec, deposit_data_list, index):
deposit_data = deposit_data_list[index] deposit_data = deposit_data_list[index]
root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list)) root = hash_tree_root(List[spec.DepositData, 2**spec.DEPOSIT_CONTRACT_TREE_DEPTH](*deposit_data_list))
tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list])) tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list]))
proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [(index + 1).to_bytes(32, 'little')] proof = (
list(get_merkle_proof(tree, item_index=index, tree_len=32))
+ [len(deposit_data_list).to_bytes(32, 'little')]
)
leaf = deposit_data.hash_tree_root() leaf = deposit_data.hash_tree_root()
assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root) assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root)
deposit = spec.Deposit(proof=proof, data=deposit_data) deposit = spec.Deposit(proof=proof, data=deposit_data)
@ -62,29 +67,69 @@ def deposit_from_context(spec, deposit_data_list, index):
return deposit, root, deposit_data_list return deposit, root, deposit_data_list
def prepare_genesis_deposits(spec, genesis_validator_count, amount, signed=False, deposit_data_list=None): def prepare_full_genesis_deposits(spec,
amount,
deposit_count,
min_pubkey_index=0,
signed=False,
deposit_data_list=None):
if deposit_data_list is None: if deposit_data_list is None:
deposit_data_list = [] deposit_data_list = []
genesis_deposits = [] genesis_deposits = []
for validator_index in range(genesis_validator_count): for pubkey_index in range(min_pubkey_index, min_pubkey_index + deposit_count):
pubkey = pubkeys[validator_index] pubkey = pubkeys[pubkey_index]
privkey = privkeys[validator_index] privkey = privkeys[pubkey_index]
# insecurely use pubkey as withdrawal key if no credentials provided # insecurely use pubkey as withdrawal key if no credentials provided
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
deposit, root, deposit_data_list = build_deposit( deposit, root, deposit_data_list = build_deposit(
spec, spec,
deposit_data_list, deposit_data_list=deposit_data_list,
pubkey, pubkey=pubkey,
privkey, privkey=privkey,
amount, amount=amount,
withdrawal_credentials, withdrawal_credentials=withdrawal_credentials,
signed, signed=signed,
) )
genesis_deposits.append(deposit) genesis_deposits.append(deposit)
return genesis_deposits, root, deposit_data_list return genesis_deposits, root, deposit_data_list
def prepare_random_genesis_deposits(spec,
deposit_count,
max_pubkey_index,
min_pubkey_index=0,
max_amount=None,
min_amount=None,
deposit_data_list=None,
rng=Random(3131)):
if max_amount is None:
max_amount = spec.MAX_EFFECTIVE_BALANCE
if min_amount is None:
min_amount = spec.MIN_DEPOSIT_AMOUNT
if deposit_data_list is None:
deposit_data_list = []
deposits = []
for _ in range(deposit_count):
pubkey_index = rng.randint(min_pubkey_index, max_pubkey_index)
pubkey = pubkeys[pubkey_index]
privkey = privkeys[pubkey_index]
amount = rng.randint(min_amount, max_amount)
random_byte = bytes([rng.randint(0, 255)])
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(random_byte)[1:]
deposit, root, deposit_data_list = build_deposit(
spec,
deposit_data_list=deposit_data_list,
pubkey=pubkey,
privkey=privkey,
amount=amount,
withdrawal_credentials=withdrawal_credentials,
signed=True,
)
deposits.append(deposit)
return deposits, root, deposit_data_list
def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_credentials=None, signed=False): def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_credentials=None, signed=False):
""" """
Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount. Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount.

View File

@ -0,0 +1,155 @@
from random import Random
from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.test.helpers.state import (
state_transition_and_sign_block,
)
from eth2spec.test.helpers.block import (
build_empty_block_for_next_slot,
)
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing
from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices
from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import build_deposit, deposit_from_context
from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True):
"""
Helper function to run a test that slashes and exits two validators
"""
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
proposer_slashing = get_valid_proposer_slashing(
spec, state, slashed_index=slash_index, signed_1=True, signed_2=True)
signed_exit = prepare_signed_exits(spec, state, [exit_index])[0]
block.body.proposer_slashings.append(proposer_slashing)
block.body.voluntary_exits.append(signed_exit)
signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid))
yield 'blocks', [signed_block]
if not valid:
yield 'post', None
return
yield 'post', state
def get_random_proposer_slashings(spec, state, rng):
num_slashings = rng.randrange(spec.MAX_PROPOSER_SLASHINGS)
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
slashings = [
get_valid_proposer_slashing(
spec, state,
slashed_index=indices.pop(rng.randrange(len(indices))), signed_1=True, signed_2=True,
)
for _ in range(num_slashings)
]
return slashings
def get_random_attester_slashings(spec, state, rng):
num_slashings = rng.randrange(spec.MAX_ATTESTER_SLASHINGS)
indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()
slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot))
slashings = [
get_valid_attester_slashing_by_indices(
spec, state,
sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, 4))]),
slot=slot_range.pop(rng.randrange(len(slot_range))),
signed_1=True, signed_2=True,
)
for _ in range(num_slashings)
]
return slashings
def get_random_attestations(spec, state, rng):
num_attestations = rng.randrange(spec.MAX_ATTESTATIONS)
attestations = [
get_valid_attestation(
spec, state,
slot=rng.randrange(state.slot - spec.SLOTS_PER_EPOCH + 1, state.slot),
signed=True,
)
for _ in range(num_attestations)
]
return attestations
def prepare_state_and_get_random_deposits(spec, state, rng):
num_deposits = rng.randrange(spec.MAX_DEPOSITS)
deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))]
deposits = []
# First build deposit data leaves
for i in range(num_deposits):
index = len(state.validators) + i
_, root, deposit_data_leaves = build_deposit(
spec,
deposit_data_leaves,
pubkeys[index],
privkeys[index],
spec.MAX_EFFECTIVE_BALANCE,
withdrawal_credentials=b'\x00' * 32,
signed=True,
)
state.eth1_data.deposit_root = root
state.eth1_data.deposit_count += num_deposits
# Then for that context, build deposits/proofs
for i in range(num_deposits):
index = len(state.validators) + i
deposit, _, _ = deposit_from_context(spec, deposit_data_leaves, index)
deposits.append(deposit)
return deposits
def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng):
num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS)
indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy())
eligible_indices = indices - to_be_slashed_indices
exit_indices = [eligible_indices.pop() for _ in range(num_exits)]
return prepare_signed_exits(spec, state, exit_indices)
def run_test_full_random_operations(spec, state, rng=Random(2080)):
# move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit
state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH
# prepare state for deposits before building block
deposits = prepare_state_and_get_random_deposits(spec, state, rng)
block = build_empty_block_for_next_slot(spec, state)
block.body.proposer_slashings = get_random_proposer_slashings(spec, state, rng)
block.body.attester_slashings = get_random_attester_slashings(spec, state, rng)
block.body.attestations = get_random_attestations(spec, state, rng)
block.body.deposits = deposits
# cannot include to be slashed indices as exits
slashed_indices = set([
slashing.signed_header_1.message.proposer_index
for slashing in block.body.proposer_slashings
])
for attester_slashing in block.body.attester_slashings:
slashed_indices = slashed_indices.union(attester_slashing.attestation_1.attesting_indices)
slashed_indices = slashed_indices.union(attester_slashing.attestation_2.attesting_indices)
block.body.voluntary_exits = get_random_voluntary_exits(spec, state, slashed_indices, rng)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'pre', state
yield 'blocks', [signed_block]
yield 'post', state

View File

@ -1,4 +1,19 @@
from eth2spec.utils import bls from eth2spec.utils import bls
from eth2spec.test.helpers.keys import privkeys
def prepare_signed_exits(spec, state, indices):
domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT)
def create_signed_exit(index):
exit = spec.VoluntaryExit(
epoch=spec.get_current_epoch(state),
validator_index=index,
)
signing_root = spec.compute_signing_root(exit, domain)
return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root))
return [create_signed_exit(index) for index in indices]
def sign_voluntary_exit(spec, state, voluntary_exit, privkey): def sign_voluntary_exit(spec, state, voluntary_exit, privkey):

View File

@ -135,20 +135,44 @@ def test_wrong_index_for_committee_signature(spec, state):
yield from run_attestation_processing(spec, state, attestation, False) yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases def reduce_state_committee_count_from_max(spec, state):
@spec_state_test """
@never_bls Modified ``state`` to ensure that it has fewer committees at each slot than ``MAX_COMMITTEES_PER_SLOT``
def test_wrong_index_for_slot(spec, state): """
while spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) >= spec.MAX_COMMITTEES_PER_SLOT: while spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) >= spec.MAX_COMMITTEES_PER_SLOT:
state.validators = state.validators[:len(state.validators) // 2] state.validators = state.validators[:len(state.validators) // 2]
state.balances = state.balances[:len(state.balances) // 2] state.balances = state.balances[:len(state.balances) // 2]
index = spec.MAX_COMMITTEES_PER_SLOT - 1
@with_all_phases
@spec_state_test
@never_bls
def test_wrong_index_for_slot_0(spec, state):
reduce_state_committee_count_from_max(spec, state)
attestation = get_valid_attestation(spec, state) attestation = get_valid_attestation(spec, state)
next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY)
attestation.data.index = index # Invalid index: current committees per slot is less than the max
attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT - 1
yield from run_attestation_processing(spec, state, attestation, False)
@with_all_phases
@spec_state_test
@never_bls
def test_wrong_index_for_slot_1(spec, state):
reduce_state_committee_count_from_max(spec, state)
current_epoch = spec.get_current_epoch(state)
committee_count = spec.get_committee_count_per_slot(state, current_epoch)
attestation = get_valid_attestation(spec, state, index=0)
next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY)
# Invalid index: off by one
attestation.data.index = committee_count
yield from run_attestation_processing(spec, state, attestation, False) yield from run_attestation_processing(spec, state, attestation, False)
@ -160,7 +184,7 @@ def test_invalid_index(spec, state):
attestation = get_valid_attestation(spec, state) attestation = get_valid_attestation(spec, state)
next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY)
# off by one (with respect to valid range) on purpose # Invalid index: off by one (with respect to valid range) on purpose
attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT
yield from run_attestation_processing(spec, state, attestation, False) yield from run_attestation_processing(spec, state, attestation, False)

View File

@ -1,6 +1,7 @@
from eth2spec.test.context import ( from eth2spec.test.context import (
spec_state_test, spec_test, spec_state_test, spec_test,
with_all_phases, single_phase, with_all_phases, single_phase,
with_phases, PHASE0,
with_custom_state, with_custom_state,
zero_activation_threshold, zero_activation_threshold,
misc_balances, low_single_balance, misc_balances, low_single_balance,
@ -12,6 +13,7 @@ from eth2spec.test.helpers.state import (
from eth2spec.test.helpers.attestations import ( from eth2spec.test.helpers.attestations import (
add_attestations_to_state, add_attestations_to_state,
get_valid_attestation, get_valid_attestation,
sign_attestation,
prepare_state_with_attestations, prepare_state_with_attestations,
) )
from eth2spec.test.helpers.rewards import leaking from eth2spec.test.helpers.rewards import leaking
@ -278,6 +280,135 @@ def test_duplicate_attestation(spec, state):
assert single_state.balances[index] == dup_state.balances[index] assert single_state.balances[index] == dup_state.balances[index]
# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged
# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test
@with_phases([PHASE0])
@spec_state_test
def test_duplicate_participants_different_attestation_1(spec, state):
"""
Same attesters get two different attestations on chain for the same inclusion delay
Earlier attestation (by list order) is correct, later has incorrect head
Note: although these are slashable, they can validly be included
"""
correct_attestation = get_valid_attestation(spec, state, signed=True)
incorrect_attestation = correct_attestation.copy()
incorrect_attestation.data.beacon_block_root = b'\x42' * 32
sign_attestation(spec, state, incorrect_attestation)
indexed_attestation = spec.get_indexed_attestation(state, correct_attestation)
participants = get_indexed_attestation_participants(spec, indexed_attestation)
assert len(participants) > 0
single_correct_state = state.copy()
dup_state = state.copy()
inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY
add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot)
add_attestations_to_state(spec, dup_state, [correct_attestation, incorrect_attestation], inclusion_slot)
next_epoch(spec, single_correct_state)
next_epoch(spec, dup_state)
# Run non-duplicate inclusion rewards for comparison. Do not yield test vectors
for _ in run_process_rewards_and_penalties(spec, single_correct_state):
pass
# Output duplicate inclusion to test vectors
yield from run_process_rewards_and_penalties(spec, dup_state)
for index in participants:
assert state.balances[index] < single_correct_state.balances[index]
assert single_correct_state.balances[index] == dup_state.balances[index]
# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged
# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test
@with_phases([PHASE0])
@spec_state_test
def test_duplicate_participants_different_attestation_2(spec, state):
"""
Same attesters get two different attestations on chain for the same inclusion delay
Earlier attestation (by list order) has incorrect head, later is correct
Note: although these are slashable, they can validly be included
"""
correct_attestation = get_valid_attestation(spec, state, signed=True)
incorrect_attestation = correct_attestation.copy()
incorrect_attestation.data.beacon_block_root = b'\x42' * 32
sign_attestation(spec, state, incorrect_attestation)
indexed_attestation = spec.get_indexed_attestation(state, correct_attestation)
participants = get_indexed_attestation_participants(spec, indexed_attestation)
assert len(participants) > 0
single_correct_state = state.copy()
dup_state = state.copy()
inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY
add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot)
add_attestations_to_state(spec, dup_state, [incorrect_attestation, correct_attestation], inclusion_slot)
next_epoch(spec, single_correct_state)
next_epoch(spec, dup_state)
# Run non-duplicate inclusion rewards for comparison. Do not yield test vectors
for _ in run_process_rewards_and_penalties(spec, single_correct_state):
pass
# Output duplicate inclusion to test vectors
yield from run_process_rewards_and_penalties(spec, dup_state)
for index in participants:
assert state.balances[index] < single_correct_state.balances[index]
# Inclusion delay does not take into account correctness so equal reward
assert single_correct_state.balances[index] == dup_state.balances[index]
# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged
# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test
@with_phases([PHASE0])
@spec_state_test
def test_duplicate_participants_different_attestation_3(spec, state):
"""
Same attesters get two different attestations on chain for *different* inclusion delay
Earlier attestation (by list order) has incorrect head, later is correct
Note: although these are slashable, they can validly be included
"""
correct_attestation = get_valid_attestation(spec, state, signed=True)
incorrect_attestation = correct_attestation.copy()
incorrect_attestation.data.beacon_block_root = b'\x42' * 32
sign_attestation(spec, state, incorrect_attestation)
indexed_attestation = spec.get_indexed_attestation(state, correct_attestation)
participants = get_indexed_attestation_participants(spec, indexed_attestation)
assert len(participants) > 0
single_correct_state = state.copy()
dup_state = state.copy()
inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY
add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot)
add_attestations_to_state(spec, dup_state, [incorrect_attestation], inclusion_slot)
add_attestations_to_state(spec, dup_state, [correct_attestation], inclusion_slot + 1)
next_epoch(spec, single_correct_state)
next_epoch(spec, dup_state)
# Run non-duplicate inclusion rewards for comparison. Do not yield test vectors
for _ in run_process_rewards_and_penalties(spec, single_correct_state):
pass
# Output duplicate inclusion to test vectors
yield from run_process_rewards_and_penalties(spec, dup_state)
for index in participants:
assert state.balances[index] < single_correct_state.balances[index]
# Inclusion delay does not take into account correctness so equal reward
assert single_correct_state.balances[index] == dup_state.balances[index]
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
# Case when some eligible attestations are slashed. Modifies attesting_balance and consequently rewards/penalties. # Case when some eligible attestations are slashed. Modifies attesting_balance and consequently rewards/penalties.

View File

@ -26,7 +26,12 @@ def slash_validators(spec, state, indices, out_epochs):
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
def test_max_penalties(spec, state): def test_max_penalties(spec, state):
slashed_count = (len(state.validators) // 3) + 1 # Slashed count to ensure that enough validators are slashed to induce maximum penalties
slashed_count = min(
(len(state.validators) // spec.PROPORTIONAL_SLASHING_MULTIPLIER) + 1,
# Can't slash more than validator count!
len(state.validators)
)
out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2) out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2)
slashed_indices = list(range(slashed_count)) slashed_indices = list(range(slashed_count))
@ -35,7 +40,7 @@ def test_max_penalties(spec, state):
total_balance = spec.get_total_active_balance(state) total_balance = spec.get_total_active_balance(state)
total_penalties = sum(state.slashings) total_penalties = sum(state.slashings)
assert total_balance // 3 <= total_penalties assert total_balance // spec.PROPORTIONAL_SLASHING_MULTIPLIER <= total_penalties
yield from run_process_slashings(spec, state) yield from run_process_slashings(spec, state)
@ -91,12 +96,12 @@ def test_scaled_penalties(spec, state):
state.slashings[5] = base + (incr * 6) state.slashings[5] = base + (incr * 6)
state.slashings[spec.EPOCHS_PER_SLASHINGS_VECTOR - 1] = base + (incr * 7) state.slashings[spec.EPOCHS_PER_SLASHINGS_VECTOR - 1] = base + (incr * 7)
slashed_count = len(state.validators) // 4 slashed_count = len(state.validators) // (spec.PROPORTIONAL_SLASHING_MULTIPLIER + 1)
assert slashed_count > 10 assert slashed_count > 10
# make the balances non-uniform. # make the balances non-uniform.
# Otherwise it would just be a simple 3/4 balance slashing. Test the per-validator scaled penalties. # Otherwise it would just be a simple balance slashing. Test the per-validator scaled penalties.
diff = spec.MAX_EFFECTIVE_BALANCE - base diff = spec.MAX_EFFECTIVE_BALANCE - base
increments = diff // incr increments = diff // incr
for i in range(10): for i in range(10):
@ -129,7 +134,7 @@ def test_scaled_penalties(spec, state):
v = state.validators[i] v = state.validators[i]
expected_penalty = ( expected_penalty = (
v.effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT v.effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT
* (3 * total_penalties) * (spec.PROPORTIONAL_SLASHING_MULTIPLIER * total_penalties)
// (total_balance) // (total_balance)
* spec.EFFECTIVE_BALANCE_INCREMENT * spec.EFFECTIVE_BALANCE_INCREMENT
) )

View File

@ -1,6 +1,7 @@
from eth2spec.test.context import PHASE0, spec_test, with_phases, single_phase from eth2spec.test.context import PHASE0, spec_test, with_phases, single_phase
from eth2spec.test.helpers.deposits import ( from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits, prepare_full_genesis_deposits,
prepare_random_genesis_deposits,
) )
@ -9,7 +10,12 @@ from eth2spec.test.helpers.deposits import (
@single_phase @single_phase
def test_initialize_beacon_state_from_eth1(spec): def test_initialize_beacon_state_from_eth1(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
deposits, deposit_root, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) deposits, deposit_root, _ = prepare_full_genesis_deposits(
spec,
spec.MAX_EFFECTIVE_BALANCE,
deposit_count,
signed=True,
)
eth1_block_hash = b'\x12' * 32 eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME eth1_timestamp = spec.MIN_GENESIS_TIME
@ -37,14 +43,18 @@ def test_initialize_beacon_state_from_eth1(spec):
@single_phase @single_phase
def test_initialize_beacon_state_some_small_balances(spec): def test_initialize_beacon_state_some_small_balances(spec):
main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
main_deposits, _, deposit_data_list = prepare_genesis_deposits(spec, main_deposit_count, main_deposits, _, deposit_data_list = prepare_full_genesis_deposits(
spec.MAX_EFFECTIVE_BALANCE, signed=True) spec, spec.MAX_EFFECTIVE_BALANCE,
deposit_count=main_deposit_count, signed=True,
)
# For deposits above, and for another deposit_count, add a balance of EFFECTIVE_BALANCE_INCREMENT # For deposits above, and for another deposit_count, add a balance of EFFECTIVE_BALANCE_INCREMENT
small_deposit_count = main_deposit_count * 2 small_deposit_count = main_deposit_count * 2
small_deposits, deposit_root, _ = prepare_genesis_deposits(spec, small_deposit_count, small_deposits, deposit_root, _ = prepare_full_genesis_deposits(
spec.MIN_DEPOSIT_AMOUNT, spec, spec.MIN_DEPOSIT_AMOUNT,
signed=True, deposit_count=small_deposit_count,
deposit_data_list=deposit_data_list) signed=True,
deposit_data_list=deposit_data_list,
)
deposits = main_deposits + small_deposits deposits = main_deposits + small_deposits
eth1_block_hash = b'\x12' * 32 eth1_block_hash = b'\x12' * 32
@ -67,3 +77,109 @@ def test_initialize_beacon_state_some_small_balances(spec):
# yield state # yield state
yield 'state', state yield 'state', state
@with_phases([PHASE0])
@spec_test
@single_phase
def test_initialize_beacon_state_one_topup_activation(spec):
# Submit all but one deposit as MAX_EFFECTIVE_BALANCE
main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1
main_deposits, _, deposit_data_list = prepare_full_genesis_deposits(
spec, spec.MAX_EFFECTIVE_BALANCE,
deposit_count=main_deposit_count, signed=True,
)
# Submit last pubkey deposit as MAX_EFFECTIVE_BALANCE - MIN_DEPOSIT_AMOUNT
partial_deposits, _, deposit_data_list = prepare_full_genesis_deposits(
spec, spec.MAX_EFFECTIVE_BALANCE - spec.MIN_DEPOSIT_AMOUNT,
deposit_count=1,
min_pubkey_index=main_deposit_count,
signed=True,
deposit_data_list=deposit_data_list,
)
# Top up thelast pubkey deposit as MIN_DEPOSIT_AMOUNT to complete the deposit
top_up_deposits, _, _ = prepare_full_genesis_deposits(
spec, spec.MIN_DEPOSIT_AMOUNT,
deposit_count=1,
min_pubkey_index=main_deposit_count,
signed=True,
deposit_data_list=deposit_data_list,
)
deposits = main_deposits + partial_deposits + top_up_deposits
eth1_block_hash = b'\x13' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME
yield 'eth1_block_hash', eth1_block_hash
yield 'eth1_timestamp', eth1_timestamp
yield 'deposits', deposits
# initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert spec.is_valid_genesis_state(state)
# yield state
yield 'state', state
@with_phases([PHASE0])
@spec_test
@single_phase
def test_initialize_beacon_state_random_invalid_genesis(spec):
# Make a bunch of random deposits
deposits, _, deposit_data_list = prepare_random_genesis_deposits(
spec,
deposit_count=20,
max_pubkey_index=10,
)
eth1_block_hash = b'\x14' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME + 1
yield 'eth1_block_hash', eth1_block_hash
yield 'eth1_timestamp', eth1_timestamp
yield 'deposits', deposits
# initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert not spec.is_valid_genesis_state(state)
yield 'state', state
@with_phases([PHASE0])
@spec_test
@single_phase
def test_initialize_beacon_state_random_valid_genesis(spec):
# Make a bunch of random deposits
random_deposits, _, deposit_data_list = prepare_random_genesis_deposits(
spec,
deposit_count=20,
min_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 5,
max_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 5,
)
# Then make spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT full deposits
full_deposits, _, _ = prepare_full_genesis_deposits(
spec,
spec.MAX_EFFECTIVE_BALANCE,
deposit_count=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT,
signed=True,
deposit_data_list=deposit_data_list
)
deposits = random_deposits + full_deposits
eth1_block_hash = b'\x15' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME + 2
yield 'eth1_block_hash', eth1_block_hash
yield 'eth1_timestamp', eth1_timestamp
yield 'deposits', deposits
# initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert spec.is_valid_genesis_state(state)
yield 'state', state

View File

@ -1,12 +1,17 @@
from eth2spec.test.context import PHASE0, spec_test, with_phases, single_phase from eth2spec.test.context import PHASE0, spec_test, with_phases, single_phase
from eth2spec.test.helpers.deposits import ( from eth2spec.test.helpers.deposits import (
prepare_genesis_deposits, prepare_full_genesis_deposits,
) )
def create_valid_beacon_state(spec): def create_valid_beacon_state(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT
deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) deposits, _, _ = prepare_full_genesis_deposits(
spec,
amount=spec.MAX_EFFECTIVE_BALANCE,
deposit_count=deposit_count,
signed=True,
)
eth1_block_hash = b'\x12' * 32 eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME eth1_timestamp = spec.MIN_GENESIS_TIME
@ -54,22 +59,17 @@ def test_is_valid_genesis_state_true_more_balance(spec):
yield from run_is_valid_genesis_state(spec, state, valid=True) yield from run_is_valid_genesis_state(spec, state, valid=True)
# TODO: not part of the genesis function yet. Erroneously merged.
# @with_phases([PHASE0])
# @spec_test
# def test_is_valid_genesis_state_false_not_enough_balance(spec):
# state = create_valid_beacon_state(spec)
# state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1
#
# yield from run_is_valid_genesis_state(spec, state, valid=False)
@with_phases([PHASE0]) @with_phases([PHASE0])
@spec_test @spec_test
@single_phase @single_phase
def test_is_valid_genesis_state_true_one_more_validator(spec): def test_is_valid_genesis_state_true_one_more_validator(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1 deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1
deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) deposits, _, _ = prepare_full_genesis_deposits(
spec,
amount=spec.MAX_EFFECTIVE_BALANCE,
deposit_count=deposit_count,
signed=True,
)
eth1_block_hash = b'\x12' * 32 eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME eth1_timestamp = spec.MIN_GENESIS_TIME
@ -83,7 +83,12 @@ def test_is_valid_genesis_state_true_one_more_validator(spec):
@single_phase @single_phase
def test_is_valid_genesis_state_false_not_enough_validator(spec): def test_is_valid_genesis_state_false_not_enough_validator(spec):
deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1
deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) deposits, _, _ = prepare_full_genesis_deposits(
spec,
amount=spec.MAX_EFFECTIVE_BALANCE,
deposit_count=deposit_count,
signed=True,
)
eth1_block_hash = b'\x12' * 32 eth1_block_hash = b'\x12' * 32
eth1_timestamp = spec.MIN_GENESIS_TIME eth1_timestamp = spec.MIN_GENESIS_TIME

View File

@ -1,3 +1,4 @@
from random import Random
from eth2spec.utils import bls from eth2spec.utils import bls
from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.state import (
@ -9,7 +10,7 @@ from eth2spec.test.helpers.block import (
sign_block, sign_block,
transition_unsigned_block, transition_unsigned_block,
) )
from eth2spec.test.helpers.keys import privkeys, pubkeys from eth2spec.test.helpers.keys import pubkeys
from eth2spec.test.helpers.attester_slashings import ( from eth2spec.test.helpers.attester_slashings import (
get_valid_attester_slashing_by_indices, get_valid_attester_slashing_by_indices,
get_valid_attester_slashing, get_valid_attester_slashing,
@ -18,7 +19,12 @@ from eth2spec.test.helpers.attester_slashings import (
from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect
from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.attestations import get_valid_attestation
from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.deposits import prepare_state_and_deposit
from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits
from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee
from eth2spec.test.helpers.multi_operations import (
run_slash_and_exit,
run_test_full_random_operations,
)
from eth2spec.test.context import ( from eth2spec.test.context import (
PHASE0, PHASE1, MINIMAL, PHASE0, PHASE1, MINIMAL,
@ -794,20 +800,6 @@ def test_attestation(spec, state):
assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root
def prepare_signed_exits(spec, state, indices):
domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT)
def create_signed_exit(index):
exit = spec.VoluntaryExit(
epoch=spec.get_current_epoch(state),
validator_index=index,
)
signing_root = spec.compute_signing_root(exit, domain)
return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root))
return [create_signed_exit(index) for index in indices]
# In phase1 a committee is computed for SHARD_COMMITTEE_PERIOD slots ago, # In phase1 a committee is computed for SHARD_COMMITTEE_PERIOD slots ago,
# exceeding the minimal-config randao mixes memory size. # exceeding the minimal-config randao mixes memory size.
# Applies to all voluntary-exit sanity block tests. # Applies to all voluntary-exit sanity block tests.
@ -895,6 +887,23 @@ def test_multiple_different_validator_exits_same_block(spec, state):
assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH
@with_all_phases
@spec_state_test
@disable_process_reveal_deadlines
def test_slash_and_exit_same_index(spec, state):
validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
yield from run_slash_and_exit(spec, state, validator_index, validator_index, valid=False)
@with_all_phases
@spec_state_test
@disable_process_reveal_deadlines
def test_slash_and_exit_diff_index(spec, state):
slash_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1]
exit_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-2]
yield from run_slash_and_exit(spec, state, slash_index, exit_index)
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
def test_balance_driven_status_transitions(spec, state): def test_balance_driven_status_transitions(spec, state):
@ -1013,3 +1022,27 @@ def test_eth1_data_votes_no_consensus(spec, state):
yield 'blocks', blocks yield 'blocks', blocks
yield 'post', state yield 'post', state
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_0(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2020))
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_1(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2021))
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_2(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2022))
@with_phases([PHASE0])
@spec_state_test
def test_full_random_operations_3(spec, state):
yield from run_test_full_random_operations(spec, state, rng=Random(2023))

View File

@ -10,6 +10,7 @@ bls = py_ecc_bls
STUB_SIGNATURE = b'\x11' * 96 STUB_SIGNATURE = b'\x11' * 96
STUB_PUBKEY = b'\x22' * 48 STUB_PUBKEY = b'\x22' * 48
Z1_PUBKEY = b'\xc0' + b'\x00' * 47
Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95
STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE) STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE)
@ -99,4 +100,7 @@ def AggregatePKs(pubkeys):
@only_with_bls(alt_return=STUB_SIGNATURE) @only_with_bls(alt_return=STUB_SIGNATURE)
def SkToPk(SK): def SkToPk(SK):
return bls.SkToPk(SK) if bls == py_ecc_bls:
return bls.SkToPk(SK)
else:
return bls.SkToPk(SK.to_bytes(32, 'big'))

View File

@ -10,7 +10,7 @@ The test data is declared in a `data.yaml` file:
input: input:
privkey: bytes32 -- the private key used for signing privkey: bytes32 -- the private key used for signing
message: bytes32 -- input message to sign (a hash) message: bytes32 -- input message to sign (a hash)
output: bytes96 -- expected signature output: BLS Signature -- expected output, single BLS signature or empty.
``` ```
All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`.

View File

@ -40,6 +40,7 @@ MESSAGES = [
bytes(b'\x56' * 32), bytes(b'\x56' * 32),
bytes(b'\xab' * 32), bytes(b'\xab' * 32),
] ]
SAMPLE_MESSAGE = b'\x12' * 32
PRIVKEYS = [ PRIVKEYS = [
# Curve order is 256 so private keys are 32 bytes at most. # Curve order is 256 so private keys are 32 bytes at most.
@ -48,16 +49,30 @@ PRIVKEYS = [
hex_to_int('0x0000000000000000000000000000000047b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665138'), hex_to_int('0x0000000000000000000000000000000047b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665138'),
hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'), hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'),
] ]
PUBKEYS = [bls.SkToPk(privkey) for privkey in PRIVKEYS]
Z1_PUBKEY = b'\xc0' + b'\x00' * 47 Z1_PUBKEY = b'\xc0' + b'\x00' * 47
NO_SIGNATURE = b'\x00' * 96 NO_SIGNATURE = b'\x00' * 96
Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95
ZERO_PRIVKEY = 0
ZERO_PRIVKEY_BYTES = b'\x00' * 32
def expect_exception(func, *args):
try:
func(*args)
except Exception:
pass
else:
raise Exception("should have raised exception")
def case01_sign(): def case01_sign():
# Valid cases
for privkey in PRIVKEYS: for privkey in PRIVKEYS:
for message in MESSAGES: for message in MESSAGES:
sig = bls.Sign(privkey, message) sig = bls.Sign(privkey, message)
assert sig == milagro_bls.Sign(to_bytes(privkey), message) # double-check with milagro
identifier = f'{int_to_hex(privkey)}_{encode_hex(message)}' identifier = f'{int_to_hex(privkey)}_{encode_hex(message)}'
yield f'sign_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { yield f'sign_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', {
'input': { 'input': {
@ -66,6 +81,16 @@ def case01_sign():
}, },
'output': encode_hex(sig) 'output': encode_hex(sig)
} }
# Edge case: privkey == 0
expect_exception(bls.Sign, ZERO_PRIVKEY, message)
expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message)
yield f'sign_case_zero_privkey', {
'input': {
'privkey': encode_hex(ZERO_PRIVKEY_BYTES),
'message': encode_hex(message),
},
'output': None
}
def case02_verify(): def case02_verify():
@ -120,42 +145,46 @@ def case02_verify():
'output': False, 'output': False,
} }
# Valid pubkey and signature with the point at infinity # Invalid pubkey and signature with the point at infinity
assert bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE) assert not bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE)
assert milagro_bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE) assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE)
yield f'verify_infinity_pubkey_and_infinity_signature', { yield f'verify_infinity_pubkey_and_infinity_signature', {
'input': { 'input': {
'pubkey': encode_hex(Z1_PUBKEY), 'pubkey': encode_hex(Z1_PUBKEY),
'message': encode_hex(message), 'message': encode_hex(SAMPLE_MESSAGE),
'signature': encode_hex(Z2_SIGNATURE), 'signature': encode_hex(Z2_SIGNATURE),
}, },
'output': True, 'output': False,
} }
def case03_aggregate(): def case03_aggregate():
for message in MESSAGES: for message in MESSAGES:
sigs = [bls.Sign(privkey, message) for privkey in PRIVKEYS] sigs = [bls.Sign(privkey, message) for privkey in PRIVKEYS]
aggregate_sig = bls.Aggregate(sigs)
assert aggregate_sig == milagro_bls.Aggregate(sigs)
yield f'aggregate_{encode_hex(message)}', { yield f'aggregate_{encode_hex(message)}', {
'input': [encode_hex(sig) for sig in sigs], 'input': [encode_hex(sig) for sig in sigs],
'output': encode_hex(bls.Aggregate(sigs)), 'output': encode_hex(aggregate_sig),
} }
# Invalid pubkeys -- len(pubkeys) == 0 # Invalid pubkeys -- len(pubkeys) == 0
try: expect_exception(bls.Aggregate, [])
bls.Aggregate([])
except Exception:
pass
else:
raise Exception("Should have been INVALID")
# No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID. # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID.
# https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8 # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-2.8
yield f'aggregate_na_signatures', { yield f'aggregate_na_signatures', {
'input': [], 'input': [],
'output': None, 'output': None,
} }
# Valid to aggregate G2 point at infinity
aggregate_sig = bls.Aggregate([Z2_SIGNATURE])
assert aggregate_sig == milagro_bls.Aggregate([Z2_SIGNATURE]) == Z2_SIGNATURE
yield f'aggregate_infinity_signature', {
'input': [encode_hex(Z2_SIGNATURE)],
'output': encode_hex(aggregate_sig),
}
def case04_fast_aggregate_verify(): def case04_fast_aggregate_verify():
for i, message in enumerate(MESSAGES): for i, message in enumerate(MESSAGES):
@ -231,6 +260,22 @@ def case04_fast_aggregate_verify():
'output': False, 'output': False,
} }
# Invalid pubkeys and signature -- pubkeys contains point at infinity
pubkeys = PUBKEYS.copy()
pubkeys_with_infinity = pubkeys + [Z1_PUBKEY]
signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS]
aggregate_signature = bls.Aggregate(signatures)
assert not bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature)
assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature)
yield f'fast_aggregate_verify_infinity_pubkey', {
'input': {
'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity],
'message': encode_hex(SAMPLE_MESSAGE),
'signature': encode_hex(aggregate_signature),
},
'output': False,
}
def case05_aggregate_verify(): def case05_aggregate_verify():
pubkeys = [] pubkeys = []
@ -295,6 +340,20 @@ def case05_aggregate_verify():
'output': False, 'output': False,
} }
# Invalid pubkeys and signature -- pubkeys contains point at infinity
pubkeys_with_infinity = pubkeys + [Z1_PUBKEY]
messages_with_sample = messages + [SAMPLE_MESSAGE]
assert not bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature)
assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature)
yield f'aggregate_verify_infinity_pubkey', {
'input': {
'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity],
'messages': [encode_hex(message) for message in messages_with_sample],
'signature': encode_hex(aggregate_signature),
},
'output': False,
}
def create_provider(handler_name: str, def create_provider(handler_name: str,
test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider: test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider:

View File

@ -1,4 +1,4 @@
py_ecc==4.0.0 py_ecc==5.0.0
eth-utils==1.6.0 eth-utils==1.6.0
../../core/gen_helpers ../../core/gen_helpers
../../../ ../../../