Merge branch 'dev' into carl_new_new_bls

This commit is contained in:
Danny Ryan 2020-01-07 15:32:55 -07:00
commit fff354d673
No known key found for this signature in database
GPG Key ID: 2765A792E42CE07A
13 changed files with 155 additions and 56 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.vy linguist-language=Python

View File

@ -91,7 +91,7 @@ install_deposit_contract_test: $(PY_SPEC_ALL_TARGETS)
compile_deposit_contract: compile_deposit_contract:
cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \ cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \
python tool/compile_deposit_contract.py contracts/validator_registration.v.py; python tool/compile_deposit_contract.py contracts/validator_registration.vy;
test_deposit_contract: test_deposit_contract:
cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \ cd $(DEPOSIT_CONTRACT_DIR); . venv/bin/activate; \

View File

@ -39,6 +39,8 @@ TARGET_AGGREGATORS_PER_COMMITTEE: 16
RANDOM_SUBNETS_PER_VALIDATOR: 1 RANDOM_SUBNETS_PER_VALIDATOR: 1
# 2**8 (= 256) # 2**8 (= 256)
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256 EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 14
# Deposit contract # Deposit contract
@ -61,13 +63,15 @@ EFFECTIVE_BALANCE_INCREMENT: 1000000000
# Initial values # Initial values
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 0, GENESIS_EPOCH is derived from this constant # Mainnet initial fork version, recommend altering for testnets
GENESIS_SLOT: 0 GENESIS_FORK_VERSION: 0x00000000
BLS_WITHDRAWAL_PREFIX: 0x00 BLS_WITHDRAWAL_PREFIX: 0x00
# Time parameters # Time parameters
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 86400 seconds (1 day)
MIN_GENESIS_DELAY: 86400
# 12 seconds # 12 seconds
SECONDS_PER_SLOT: 12 SECONDS_PER_SLOT: 12
# 2**0 (= 1) slots 12 seconds # 2**0 (= 1) slots 12 seconds

View File

@ -39,6 +39,8 @@ TARGET_AGGREGATORS_PER_COMMITTEE: 16
RANDOM_SUBNETS_PER_VALIDATOR: 1 RANDOM_SUBNETS_PER_VALIDATOR: 1
# 2**8 (= 256) # 2**8 (= 256)
EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256 EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION: 256
# 14 (estimate from Eth1 mainnet)
SECONDS_PER_ETH1_BLOCK: 14
# Deposit contract # Deposit contract
@ -61,13 +63,15 @@ EFFECTIVE_BALANCE_INCREMENT: 1000000000
# Initial values # Initial values
# --------------------------------------------------------------- # ---------------------------------------------------------------
# 0, GENESIS_EPOCH is derived from this constant # Highest byte set to 0x01 to avoid collisions with mainnet versioning
GENESIS_SLOT: 0 GENESIS_FORK_VERSION: 0x00000001
BLS_WITHDRAWAL_PREFIX: 0x00 BLS_WITHDRAWAL_PREFIX: 0x00
# Time parameters # Time parameters
# --------------------------------------------------------------- # ---------------------------------------------------------------
# [customized] Faster to spin up testnets, but does not give validator reasonable warning time for genesis
MIN_GENESIS_DELAY: 300
# [customized] Faster for testing purposes # [customized] Faster for testing purposes
SECONDS_PER_SLOT: 6 SECONDS_PER_SLOT: 6
# 2**0 (= 1) slots 6 seconds # 2**0 (= 1) slots 6 seconds

View File

@ -5,7 +5,7 @@ DIR = os.path.dirname(__file__)
def get_deposit_contract_code(): def get_deposit_contract_code():
file_path = os.path.join(DIR, './../../contracts/validator_registration.v.py') file_path = os.path.join(DIR, './../../contracts/validator_registration.vy')
deposit_contract_code = open(file_path).read() deposit_contract_code = open(file_path).read()
return deposit_contract_code return deposit_contract_code

View File

@ -159,10 +159,11 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value | | Name | Value |
| - | - | | - | - |
| `GENESIS_SLOT` | `Slot(0)` |
| `GENESIS_EPOCH` | `Epoch(0)` |
| `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` | | `FAR_FUTURE_EPOCH` | `Epoch(2**64 - 1)` |
| `BASE_REWARDS_PER_EPOCH` | `4` | | `BASE_REWARDS_PER_EPOCH` | `4` |
| `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) | | `DEPOSIT_CONTRACT_TREE_DEPTH` | `2**5` (= 32) |
| `SECONDS_PER_DAY` | `86400` |
| `JUSTIFICATION_BITS_LENGTH` | `4` | | `JUSTIFICATION_BITS_LENGTH` | `4` |
| `ENDIANNESS` | `'little'` | | `ENDIANNESS` | `'little'` |
@ -198,14 +199,14 @@ The following values are (non-configurable) constants used throughout the specif
| Name | Value | | Name | Value |
| - | - | | - | - |
| `GENESIS_SLOT` | `Slot(0)` | | `GENESIS_FORK_VERSION` | `Version('0x00000000')` |
| `GENESIS_EPOCH` | `Epoch(0)` |
| `BLS_WITHDRAWAL_PREFIX` | `Bytes1('0x00')` | | `BLS_WITHDRAWAL_PREFIX` | `Bytes1('0x00')` |
### Time parameters ### Time parameters
| Name | Value | Unit | Duration | | Name | Value | Unit | Duration |
| - | - | :-: | :-: | | - | - | :-: | :-: |
| `MIN_GENESIS_DELAY` | `86400` | seconds | 1 day |
| `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds | | `SECONDS_PER_SLOT` | `12` | seconds | 12 seconds |
| `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `2**0` (= 1) | slots | 12 seconds |
| `SLOTS_PER_EPOCH` | `2**5` (= 32) | slots | 6.4 minutes | | `SLOTS_PER_EPOCH` | `2**5` (= 32) | slots | 6.4 minutes |
@ -789,7 +790,7 @@ def compute_activation_exit_epoch(epoch: Epoch) -> Epoch:
#### `compute_domain` #### `compute_domain`
```python ```python
def compute_domain(domain_type: DomainType, fork_version: Version=Version()) -> Domain: def compute_domain(domain_type: DomainType, fork_version: Version=GENESIS_FORK_VERSION) -> Domain:
""" """
Return the domain for the ``domain_type`` and ``fork_version``. Return the domain for the ``domain_type`` and ``fork_version``.
""" """
@ -1086,8 +1087,14 @@ Before the Ethereum 2.0 genesis has been triggered, and for every Ethereum 1.0 b
def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32, def initialize_beacon_state_from_eth1(eth1_block_hash: Bytes32,
eth1_timestamp: uint64, eth1_timestamp: uint64,
deposits: Sequence[Deposit]) -> BeaconState: deposits: Sequence[Deposit]) -> BeaconState:
fork = Fork(
previous_version=GENESIS_FORK_VERSION,
current_version=GENESIS_FORK_VERSION,
epoch=GENESIS_EPOCH,
)
state = BeaconState( state = BeaconState(
genesis_time=eth1_timestamp - eth1_timestamp % SECONDS_PER_DAY + 2 * SECONDS_PER_DAY, genesis_time=eth1_timestamp - eth1_timestamp % MIN_GENESIS_DELAY + 2 * MIN_GENESIS_DELAY,
fork=fork,
eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)), eth1_data=Eth1Data(block_hash=eth1_block_hash, deposit_count=len(deposits)),
latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())), latest_block_header=BeaconBlockHeader(body_root=hash_tree_root(BeaconBlockBody())),
randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy randao_mixes=[eth1_block_hash] * EPOCHS_PER_HISTORICAL_VECTOR, # Seed RANDAO with Eth1 entropy

View File

@ -61,6 +61,6 @@ Every Ethereum 1.0 deposit emits a `DepositEvent` log for consumption by the bea
## Vyper code ## Vyper code
The deposit contract source code, written in Vyper, is available [here](../../deposit_contract/contracts/validator_registration.v.py). The deposit contract source code, written in Vyper, is available [here](../../deposit_contract/contracts/validator_registration.vy).
*Note*: To save on gas, the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof. *Note*: To save on gas, the deposit contract uses a progressive Merkle root calculation algorithm that requires only O(log(n)) storage. See [here](https://github.com/ethereum/research/blob/master/beacon_chain_impl/progressive_merkle_tree.py) for a Python implementation, and [here](https://github.com/runtimeverification/verified-smart-contracts/blob/master/deposit/formal-incremental-merkle-tree-algorithm.pdf) for a formal correctness proof.

View File

@ -93,6 +93,7 @@ It consists of four main sections:
- [Why are messages length-prefixed with a protobuf varint in the SSZ-encoding?](#why-are-messages-length-prefixed-with-a-protobuf-varint-in-the-ssz-encoding) - [Why are messages length-prefixed with a protobuf varint in the SSZ-encoding?](#why-are-messages-length-prefixed-with-a-protobuf-varint-in-the-ssz-encoding)
- [Why do we version protocol strings with ordinals instead of semver?](#why-do-we-version-protocol-strings-with-ordinals-instead-of-semver) - [Why do we version protocol strings with ordinals instead of semver?](#why-do-we-version-protocol-strings-with-ordinals-instead-of-semver)
- [Why is it called Req/Resp and not RPC?](#why-is-it-called-reqresp-and-not-rpc) - [Why is it called Req/Resp and not RPC?](#why-is-it-called-reqresp-and-not-rpc)
- [Why do we allow empty responses in block requests?](#why-do-we-allow-empty-responses-in-block-requests)
- [Discovery](#discovery) - [Discovery](#discovery)
- [Why are we using discv5 and not libp2p Kademlia DHT?](#why-are-we-using-discv5-and-not-libp2p-kademlia-dht) - [Why are we using discv5 and not libp2p Kademlia DHT?](#why-are-we-using-discv5-and-not-libp2p-kademlia-dht)
- [What is the difference between an ENR and a multiaddr, and why are we using ENRs?](#what-is-the-difference-between-an-enr-and-a-multiaddr-and-why-are-we-using-enrs) - [What is the difference between an ENR and a multiaddr, and why are we using ENRs?](#what-is-the-difference-between-an-enr-and-a-multiaddr-and-why-are-we-using-enrs)
@ -322,14 +323,14 @@ Request/response messages MUST adhere to the encoding specified in the protocol
``` ```
request ::= <encoding-dependent-header> | <encoded-payload> request ::= <encoding-dependent-header> | <encoded-payload>
response ::= <response_chunk>+ response ::= <response_chunk>*
response_chunk ::= <result> | <encoding-dependent-header> | <encoded-payload> response_chunk ::= <result> | <encoding-dependent-header> | <encoded-payload>
result ::= “0” | “1” | “2” | [“128” ... ”255”] result ::= “0” | “1” | “2” | [“128” ... ”255”]
``` ```
The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security. The encoding-dependent header may carry metadata or assertions such as the encoded payload length, for integrity and attack proofing purposes. Because req/resp streams are single-use and stream closures implicitly delimit the boundaries, it is not strictly necessary to length-prefix payloads; however, certain encodings like SSZ do, for added security.
A `response` is formed by one or more `response_chunk`s. The exact request determines whether a response consists of a single `response_chunk` or possibly many. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The encoded-payload of a `response_chunk` has a maximum uncompressed byte size of `MAX_CHUNK_SIZE`. A `response` is formed by zero or more `response_chunk`s. Responses that consist of a single SSZ-list (such as `BlocksByRange` and `BlocksByRoot`) send each list item as a `response_chunk`. All other response types (non-Lists) send a single `response_chunk`. The encoded-payload of a `response_chunk` has a maximum uncompressed byte size of `MAX_CHUNK_SIZE`.
Clients MUST ensure the each encoded payload of a `response_chunk` is less than or equal to `MAX_CHUNK_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance. Clients MUST ensure the each encoded payload of a `response_chunk` is less than or equal to `MAX_CHUNK_SIZE`; if not, they SHOULD reset the stream immediately. Clients tracking peer reputation MAY decrement the score of the misbehaving peer under this circumstance.
@ -352,7 +353,7 @@ The responder MUST:
1. Use the encoding strategy to read the optional header. 1. Use the encoding strategy to read the optional header.
2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure. 2. If there are any length assertions for length `N`, it should read exactly `N` bytes from the stream, at which point an EOF should arise (no more bytes). Should this not be the case, it should be treated as a failure.
3. Deserialize the expected type, and process the request. 3. Deserialize the expected type, and process the request.
4. Write the response which may consist of one or more `response_chunk`s (result, optional header, payload). 4. Write the response which may consist of zero or more `response_chunk`s (result, optional header, payload).
5. Close their write side of the stream. At this point, the stream will be fully closed. 5. Close their write side of the stream. At this point, the stream will be fully closed.
If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets. If steps (1), (2), or (3) fail due to invalid, malformed, or inconsistent data, the responder MUST respond in error. Clients tracking peer reputation MAY record such failures, as well as unexpected events, e.g. early stream resets.
@ -406,7 +407,7 @@ All messages that contain only a single field MUST be encoded directly as the ty
Responses that are SSZ-lists (for example `[]SignedBeaconBlock`) send their Responses that are SSZ-lists (for example `[]SignedBeaconBlock`) send their
constituents individually as `response_chunk`s. For example, the constituents individually as `response_chunk`s. For example, the
`[]SignedBeaconBlock` response type sends one or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload. `[]SignedBeaconBlock` response type sends zero or more `response_chunk`s. Each _successful_ `response_chunk` contains a single `SignedBeaconBlock` payload.
### Messages ### Messages
@ -496,7 +497,7 @@ Requests count beacon blocks from the peer starting from `start_slot` on the cha
The request MUST be encoded as an SSZ-container. The request MUST be encoded as an SSZ-container.
The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload.
`BeaconBlocksByRange` is primarily used to sync historical blocks. `BeaconBlocksByRange` is primarily used to sync historical blocks.
@ -504,7 +505,7 @@ Clients MUST support requesting blocks since the start of the weak subjectivity
Clients MUST support `head_block_root` values since the latest finalized epoch. Clients MUST support `head_block_root` values since the latest finalized epoch.
Clients MUST respond with at least one block, if they have it. Clients MUST respond with at least one block, if they have it and it exists in the range. Clients MAY limit the number of blocks in the response.
Clients MUST order blocks by increasing slot number. Clients MUST order blocks by increasing slot number.
@ -534,11 +535,11 @@ Requests blocks by block root (= `hash_tree_root(SignedBeaconBlock.message)`). T
The request MUST be encoded as an SSZ-field. The request MUST be encoded as an SSZ-field.
The response MUST consist of at least one `response_chunk` and MAY consist of many. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload.
Clients MUST support requesting blocks since the latest finalized epoch. Clients MUST support requesting blocks since the latest finalized epoch.
Clients MUST respond with at least one block, if they have it. Clients MUST respond with at least one block, if they have it. Clients MAY limit the number of blocks in the response.
## The discovery domain: discv5 ## The discovery domain: discv5
@ -853,6 +854,26 @@ For this reason, we remove and replace semver with ordinals that require explici
Req/Resp is used to avoid confusion with JSON-RPC and similar user-client interaction mechanisms. Req/Resp is used to avoid confusion with JSON-RPC and similar user-client interaction mechanisms.
### Why do we allow empty responses in block requests?
When requesting blocks by range or root, it may happen that there are no blocks in the selected range or the responding node does not have the requested blocks.
Thus, it may happen that we need to transmit an empty list - there are several ways to encode this:
0) Close the stream without sending any data
1) Add a `null` option to the `success` response, for example by introducing an additional byte
2) Respond with an error result, using a specific error code for "No data"
Semantically, it is not an error that a block is missing during a slot making option 2 unnatural.
Option 1 allows allows the responder to signal "no block", but this information may be wrong - for example in the case of a malicious node.
Under option 0, there is no way for a client to distinguish between a slot without a block and an incomplete response, but given that it already must contain logic to handle the uncertainty of a malicious peer, option 0 was chosen. Clients should mark any slots missing blocks as unknown until they can be verified as not containing a block by successive blocks.
Assuming option 0 with no special `null` encoding, consider a request for slots `2, 3, 4` - if there was no block produced at slot 4, the response would be `2, 3, EOF`. Now consider the same situation, but where only `4` is requested - closing the stream with only `EOF` (without any `response_chunk`) is consistent.
Failing to provide blocks that nodes "should" have is reason to trust a peer less - for example, if a particular peer gossips a block, it should have access to its parent. If a request for the parent fails, it's indicative of poor peer quality since peers should validate blocks before gossiping them.
## Discovery ## Discovery
### Why are we using discv5 and not libp2p Kademlia DHT? ### Why are we using discv5 and not libp2p Kademlia DHT?

View File

@ -31,6 +31,8 @@
- [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody) - [Constructing the `BeaconBlockBody`](#constructing-the-beaconblockbody)
- [Randao reveal](#randao-reveal) - [Randao reveal](#randao-reveal)
- [Eth1 Data](#eth1-data) - [Eth1 Data](#eth1-data)
- [`Eth1Block`](#eth1block)
- [`get_eth1_data`](#get_eth1_data)
- [Proposer slashings](#proposer-slashings) - [Proposer slashings](#proposer-slashings)
- [Attester slashings](#attester-slashings) - [Attester slashings](#attester-slashings)
- [Attestations](#attestations) - [Attestations](#attestations)
@ -85,6 +87,7 @@ All terminology, constants, functions, and protocol mechanics defined in the [Ph
| `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | | | `TARGET_AGGREGATORS_PER_COMMITTEE` | `2**4` (= 16) | validators | |
| `RANDOM_SUBNETS_PER_VALIDATOR` | `2**0` (= 1) | subnets | | | `RANDOM_SUBNETS_PER_VALIDATOR` | `2**0` (= 1) | subnets | |
| `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours | | `EPOCHS_PER_RANDOM_SUBNET_SUBSCRIPTION` | `2**8` (= 256) | epochs | ~27 hours |
| `SECONDS_PER_ETH1_BLOCK` | `14` | seconds | |
## Becoming a validator ## Becoming a validator
@ -117,7 +120,7 @@ To submit a deposit:
- Set `deposit_data.withdrawal_credentials` to `withdrawal_credentials`. - Set `deposit_data.withdrawal_credentials` to `withdrawal_credentials`.
- Set `deposit_data.amount` to `amount`. - Set `deposit_data.amount` to `amount`.
- Let `deposit_message` be a `DepositMessage` with all the `DepositData` contents except the `signature`. - Let `deposit_message` be a `DepositMessage` with all the `DepositData` contents except the `signature`.
- Let `signature` be the result of `Sign` of the `compute_signing_root(deposit_message, domain)` with `domain=compute_domain(DOMAIN_DEPOSIT)`. (Deposits are valid regardless of fork version, `compute_domain` will default to zeroes there). - Let `signature` be the result of `bls.Sign` of the `compute_signing_root(deposit_message, domain)` with `domain=compute_domain(DOMAIN_DEPOSIT)`. (_Warning_: Deposits _must_ be signed with `GENESIS_FORK_VERSION`, calling `compute_domain` without a second argument defaults to the correct version).
- Let `deposit_data_root` be `hash_tree_root(deposit_data)`. - Let `deposit_data_root` be `hash_tree_root(deposit_data)`.
- Send a transaction on the Ethereum 1.0 chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32)` along with a deposit of `amount` Gwei. - Send a transaction on the Ethereum 1.0 chain to `DEPOSIT_CONTRACT_ADDRESS` executing `def deposit(pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32)` along with a deposit of `amount` Gwei.
@ -240,28 +243,55 @@ def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) ->
##### Eth1 Data ##### Eth1 Data
The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 data. This recent data contains an Eth1 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth1 block. If over half of the block proposers in the current Eth1 voting period vote for the same `eth1_data` then `state.eth1_data` updates at the end of the voting period. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`. The `block.body.eth1_data` field is for block proposers to vote on recent Eth1 data. This recent data contains an Eth1 block hash as well as the associated deposit root (as calculated by the `get_deposit_root()` method of the deposit contract) and deposit count after execution of the corresponding Eth1 block. If over half of the block proposers in the current Eth1 voting period vote for the same `eth1_data` then `state.eth1_data` updates immediately allowing new deposits to be processed. Each deposit in `block.body.deposits` must verify against `state.eth1_data.eth1_deposit_root`.
Let `get_eth1_data(distance: uint64) -> Eth1Data` be the (subjective) function that returns the Eth1 data at distance `distance` relative to the Eth1 head at the start of the current Eth1 voting period. Let `previous_eth1_distance` be the distance relative to the Eth1 block corresponding to `eth1_data.block_hash` found in the state at the _start_ of the current Eth1 voting period. Note that `eth1_data` can be updated in the middle of a voting period and thus the starting `eth1_data.block_hash` must be stored separately. ###### `Eth1Block`
An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state, previous_eth1_distance)` where: Let `Eth1Block` be an abstract object representing Eth1 blocks with the `timestamp` field available.
```python ```python
def get_eth1_vote(state: BeaconState, previous_eth1_distance: uint64) -> Eth1Data: class Eth1Block(Container):
new_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, 2 * ETH1_FOLLOW_DISTANCE)] timestamp: uint64
all_eth1_data = [get_eth1_data(distance) for distance in range(ETH1_FOLLOW_DISTANCE, previous_eth1_distance)] # All other eth1 block fields
```
period_tail = state.slot % SLOTS_PER_ETH1_VOTING_PERIOD >= integer_squareroot(SLOTS_PER_ETH1_VOTING_PERIOD) ###### `get_eth1_data`
if period_tail:
votes_to_consider = all_eth1_data Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block.
else:
votes_to_consider = new_eth1_data An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state)` where:
```python
def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64:
return state.genesis_time + slot * SECONDS_PER_SLOT
```
```python
def voting_period_start_time(state: BeaconState) -> uint64:
eth1_voting_period_start_slot = Slot(state.slot - state.slot % SLOTS_PER_ETH1_VOTING_PERIOD)
return compute_time_at_slot(state, eth1_voting_period_start_slot)
```
```python
def is_candidate_block(block: Eth1Block, period_start: uint64) -> bool:
return (
block.timestamp <= period_start - SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE
and block.timestamp >= period_start - SECONDS_PER_ETH1_BLOCK * ETH1_FOLLOW_DISTANCE * 2
)
```
```python
def get_eth1_vote(state: BeaconState, eth1_chain: Sequence[Eth1Block]) -> Eth1Data:
period_start = voting_period_start_time(state)
# `eth1_chain` abstractly represents all blocks in the eth1 chain.
votes_to_consider = [get_eth1_data(block) for block in eth1_chain if
is_candidate_block(block, period_start)]
valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider] valid_votes = [vote for vote in state.eth1_data_votes if vote in votes_to_consider]
return max( return max(
valid_votes, valid_votes,
key=lambda v: (valid_votes.count(v), -all_eth1_data.index(v)), # Tiebreak by smallest distance key=lambda v: (valid_votes.count(v), -valid_votes.index(v)), # Tiebreak by smallest distance
default=get_eth1_data(ETH1_FOLLOW_DISTANCE), default=get_eth1_data(ETH1_FOLLOW_DISTANCE),
) )
``` ```
@ -320,7 +350,7 @@ def get_block_signature(state: BeaconState, header: BeaconBlockHeader, privkey:
A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`. A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`.
A validator should create and broadcast the `attestation` to the associated attestation subnet one-third of the way through the `slot` during which the validator is assigned―that is, `SECONDS_PER_SLOT / 3` seconds after the start of `slot`. A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid block from the expected block proposer for the assigned `slot` or (b) one-third of the `slot` hash transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_.
*Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded fork, and should be made. *Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded fork, and should be made.

View File

@ -20,7 +20,7 @@ def test_initialize_beacon_state_from_eth1(spec):
# initialize beacon_state # initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.SECONDS_PER_DAY + 2 * spec.SECONDS_PER_DAY assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY
assert len(state.validators) == deposit_count assert len(state.validators) == deposit_count
assert state.eth1_data.deposit_root == deposit_root assert state.eth1_data.deposit_root == deposit_root
assert state.eth1_data.deposit_count == deposit_count assert state.eth1_data.deposit_count == deposit_count
@ -55,7 +55,7 @@ def test_initialize_beacon_state_some_small_balances(spec):
# initialize beacon_state # initialize beacon_state
state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits)
assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.SECONDS_PER_DAY + 2 * spec.SECONDS_PER_DAY assert state.genesis_time == eth1_timestamp - eth1_timestamp % spec.MIN_GENESIS_DELAY + 2 * spec.MIN_GENESIS_DELAY
assert len(state.validators) == small_deposit_count assert len(state.validators) == small_deposit_count
assert state.eth1_data.deposit_root == deposit_root assert state.eth1_data.deposit_root == deposit_root
assert state.eth1_data.deposit_count == len(deposits) assert state.eth1_data.deposit_count == len(deposits)

View File

@ -5,46 +5,42 @@ from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from eth2spec.utils.ssz.ssz_typing import List from eth2spec.utils.ssz.ssz_typing import List
def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=None, signed=False): def build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=False):
deposit_data = spec.DepositData( deposit_data = spec.DepositData(
pubkey=pubkey, pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials, withdrawal_credentials=withdrawal_credentials,
amount=amount, amount=amount,
) )
if signed: if signed:
sign_deposit_data(spec, deposit_data, privkey, state) sign_deposit_data(spec, deposit_data, privkey)
return deposit_data return deposit_data
def sign_deposit_data(spec, deposit_data, privkey, state=None): def sign_deposit_data(spec, deposit_data, privkey):
if state is None:
# Genesis
domain = spec.compute_domain(spec.DOMAIN_DEPOSIT)
else:
domain = spec.get_domain(
state,
spec.DOMAIN_DEPOSIT,
)
deposit_message = spec.DepositMessage( deposit_message = spec.DepositMessage(
pubkey=deposit_data.pubkey, pubkey=deposit_data.pubkey,
withdrawal_credentials=deposit_data.withdrawal_credentials, withdrawal_credentials=deposit_data.withdrawal_credentials,
amount=deposit_data.amount) amount=deposit_data.amount)
domain = spec.compute_domain(spec.DOMAIN_DEPOSIT)
signing_root = spec.compute_signing_root(deposit_message, domain) signing_root = spec.compute_signing_root(deposit_message, domain)
deposit_data.signature = bls.Sign(privkey, signing_root) deposit_data.signature = bls.Sign(privkey, signing_root)
def build_deposit(spec, def build_deposit(spec,
state,
deposit_data_list, deposit_data_list,
pubkey, pubkey,
privkey, privkey,
amount, amount,
withdrawal_credentials, withdrawal_credentials,
signed): signed):
deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, state=state, signed=signed) deposit_data = build_deposit_data(spec, pubkey, privkey, amount, withdrawal_credentials, signed=signed)
index = len(deposit_data_list) index = len(deposit_data_list)
deposit_data_list.append(deposit_data) deposit_data_list.append(deposit_data)
return deposit_from_context(spec, deposit_data_list, index)
def deposit_from_context(spec, 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)) + [(index + 1).to_bytes(32, 'little')]
@ -66,7 +62,6 @@ def prepare_genesis_deposits(spec, genesis_validator_count, amount, signed=False
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,
None,
deposit_data_list, deposit_data_list,
pubkey, pubkey,
privkey, privkey,
@ -94,7 +89,6 @@ def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_c
deposit, root, deposit_data_list = build_deposit( deposit, root, deposit_data_list = build_deposit(
spec, spec,
state,
deposit_data_list, deposit_data_list,
pubkey, pubkey,
privkey, privkey,

View File

@ -3,9 +3,10 @@ from eth2spec.test.helpers.deposits import (
build_deposit, build_deposit,
prepare_state_and_deposit, prepare_state_and_deposit,
sign_deposit_data, sign_deposit_data,
) deposit_from_context)
from eth2spec.test.helpers.state import get_balance from eth2spec.test.helpers.state import get_balance
from eth2spec.test.helpers.keys import privkeys, pubkeys from eth2spec.test.helpers.keys import privkeys, pubkeys
from eth2spec.utils import bls
def run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True): def run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True):
@ -93,6 +94,45 @@ def test_new_deposit_over_max(spec, state):
yield from run_deposit_processing(spec, state, deposit, validator_index) yield from run_deposit_processing(spec, state, deposit, validator_index)
@with_all_phases
@spec_state_test
@always_bls
def test_invalid_sig_other_version(spec, state):
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
pubkey = pubkeys[validator_index]
privkey = privkeys[validator_index]
withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:]
# Go through the effort of manually signing, not something normally done. This sig domain will be invalid.
deposit_message = spec.DepositMessage(pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount)
domain = spec.compute_domain(domain_type=spec.DOMAIN_DEPOSIT, fork_version=spec.Version('0xaabbccdd'))
deposit_data = spec.DepositData(
pubkey=pubkey, withdrawal_credentials=withdrawal_credentials, amount=amount,
signature=bls.Sign(privkey, spec.compute_signing_root(deposit_message, domain))
)
deposit, root, _ = deposit_from_context(spec, [deposit_data], 0)
state.eth1_deposit_index = 0
state.eth1_data.deposit_root = root
state.eth1_data.deposit_count = 1
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=False)
@with_all_phases
@spec_state_test
@always_bls
def test_valid_sig_but_forked_state(spec, state):
validator_index = len(state.validators)
amount = spec.MAX_EFFECTIVE_BALANCE
# deposits will always be valid, regardless of the current fork
state.fork.current_version = spec.Version('0x1234abcd')
deposit = prepare_state_and_deposit(spec, state, validator_index, amount, signed=True)
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=True, effective=True)
@with_all_phases @with_all_phases
@spec_state_test @spec_state_test
@always_bls @always_bls
@ -155,7 +195,6 @@ def test_wrong_deposit_for_deposit_count(spec, state):
privkey_1 = privkeys[index_1] privkey_1 = privkeys[index_1]
_, _, deposit_data_leaves = build_deposit( _, _, deposit_data_leaves = build_deposit(
spec, spec,
state,
deposit_data_leaves, deposit_data_leaves,
pubkey_1, pubkey_1,
privkey_1, privkey_1,
@ -171,7 +210,6 @@ def test_wrong_deposit_for_deposit_count(spec, state):
privkey_2 = privkeys[index_2] privkey_2 = privkeys[index_2]
deposit_2, root_2, deposit_data_leaves = build_deposit( deposit_2, root_2, deposit_data_leaves = build_deposit(
spec, spec,
state,
deposit_data_leaves, deposit_data_leaves,
pubkey_2, pubkey_2,
privkey_2, privkey_2,
@ -197,6 +235,6 @@ def test_bad_merkle_proof(spec, state):
# mess up merkle branch # mess up merkle branch
deposit.proof[5] = spec.Bytes32() deposit.proof[5] = spec.Bytes32()
sign_deposit_data(spec, deposit.data, privkeys[validator_index], state=state) sign_deposit_data(spec, deposit.data, privkeys[validator_index])
yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False) yield from run_deposit_processing(spec, state, deposit, validator_index, valid=False)