Merge branch 'dev' into carl_new_new_bls
This commit is contained in:
commit
fff354d673
|
@ -0,0 +1 @@
|
||||||
|
*.vy linguist-language=Python
|
2
Makefile
2
Makefile
|
@ -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; \
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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?
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue