Merge branch 'dev' into testgenphase1

This commit is contained in:
protolambda 2020-09-09 23:43:26 +02:00
commit 28137a6176
No known key found for this signature in database
GPG Key ID: EC89FDBB2B4C7623
40 changed files with 642 additions and 325 deletions

View File

@ -44,32 +44,19 @@ commands:
venv_name: v22-pyspec
reqs_checksum: cache-{{ checksum "setup.py" }}
venv_path: ./venv
restore_deposit_contract_compiler_cached_venv:
description: "Restore the venv from cache for the deposit contract compiler"
steps:
- restore_cached_venv:
venv_name: v23-deposit-contract-compiler
reqs_checksum: cache-{{ checksum "deposit_contract/compiler/requirements.txt" }}
save_deposit_contract_compiler_cached_venv:
description: "Save the venv to cache for later use of the deposit contract compiler"
steps:
- save_cached_venv:
venv_name: v23-deposit-contract-compiler
reqs_checksum: cache-{{ checksum "deposit_contract/compiler/requirements.txt" }}
venv_path: ./deposit_contract/compiler/venv
restore_deposit_contract_tester_cached_venv:
description: "Restore the venv from cache for the deposit contract tester"
steps:
- restore_cached_venv:
venv_name: v22-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "deposit_contract/tester/requirements.txt" }}
venv_name: v23-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "solidity_deposit_contract/web3_tester/requirements.txt" }}
save_deposit_contract_tester_cached_venv:
description: "Save the venv to cache for later use of the deposit contract tester"
steps:
- save_cached_venv:
venv_name: v22-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "deposit_contract/tester/requirements.txt" }}
venv_path: ./deposit_contract/tester/venv
venv_name: v23-deposit-contract-tester
reqs_checksum: cache-{{ checksum "setup.py" }}-{{ checksum "solidity_deposit_contract/web3_tester/requirements.txt" }}
venv_path: ./solidity_deposit_contract/web3_tester/venv
jobs:
checkout_specs:
docker:
@ -145,20 +132,49 @@ jobs:
- run:
name: Run linter
command: make lint
install_deposit_contract_compiler:
build_deposit_contract:
docker:
# The deposit contract compiler is pinned to python 3.7 because of the vyper version pin.
- image: circleci/python:3.7
working_directory: ~/specs-repo
- image: ethereum/solc:0.6.11-alpine
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_compiler_cached_venv
- checkout
- run:
name: Install deposit contract compiler requirements
command: make install_deposit_contract_compiler
- save_deposit_contract_compiler_cached_venv
install_deposit_contract_tester:
name: Install build essentials
command: |
apk update
apk add git make
- run:
name: Compile the contract
command: |
make compile_deposit_contract
git diff --color --exit-code
- persist_to_workspace:
root: .
paths:
- ./solidity_deposit_contract/deposit_contract.json
- ./build/combined.json
- ./solidity_deposit_contract/lib
test_deposit_contract:
docker:
- image: nixorg/nix:circleci
steps:
- checkout
- restore_cache:
key: nix-store-test-v2
- attach_workspace:
at: /tmp/
- run:
name: Test the contract
command: |
mkdir build
cp -r /tmp/build/* build
cp -r /tmp/solidity_deposit_contract/lib/* ./solidity_deposit_contract/lib
cp -r /tmp/solidity_deposit_contract/deposit_contract.json ./solidity_deposit_contract/deposit_contract.json
nix-shell --command 'make test_deposit_contract' ./solidity_deposit_contract/shell.nix
- save_cache:
key: nix-store-test-v2
paths:
- /nix
install_deposit_contract_web3_tester:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
@ -168,20 +184,9 @@ jobs:
- restore_deposit_contract_tester_cached_venv
- run:
name: Install deposit contract tester requirements
command: make install_deposit_contract_tester
command: make install_deposit_contract_web3_tester
- save_deposit_contract_tester_cached_venv
test_compile_deposit_contract:
docker:
- image: circleci/python:3.7
working_directory: ~/specs-repo
steps:
- restore_cache:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_compiler_cached_venv
- run:
name: Run deposit contract compile test
command: make test_compile_deposit_contract
test_deposit_contract:
test_deposit_contract_web3_tests:
docker:
- image: circleci/python:3.8
working_directory: ~/specs-repo
@ -190,9 +195,8 @@ jobs:
key: v3-specs-repo-{{ .Branch }}-{{ .Revision }}
- restore_deposit_contract_tester_cached_venv
- run:
name: Run deposit contract test
command: make test_deposit_contract
name: Run deposit contract test with web3.py
command: make test_deposit_contract_web3_tests
workflows:
version: 2.1
test_spec:
@ -209,15 +213,15 @@ workflows:
- lint:
requires:
- test
- install_deposit_contract_compiler:
- install_deposit_contract_web3_tester:
requires:
- checkout_specs
- test_compile_deposit_contract:
- test_deposit_contract_web3_tests:
requires:
- install_deposit_contract_compiler
- install_deposit_contract_tester:
requires:
- checkout_specs
- install_deposit_contract_web3_tester
build_and_test_deposit_contract:
jobs:
- build_deposit_contract
- test_deposit_contract:
requires:
- install_deposit_contract_tester
- build_deposit_contract

2
.gitattributes vendored
View File

@ -1 +1 @@
*.vy linguist-language=Python
*.sol linguist-language=Solidity

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/ds-test"]
path = solidity_deposit_contract/lib/ds-test
url = https://github.com/dapphub/ds-test

View File

@ -4,8 +4,10 @@ TEST_LIBS_DIR = ./tests/core
PY_SPEC_DIR = $(TEST_LIBS_DIR)/pyspec
TEST_VECTOR_DIR = ../eth2.0-spec-tests/tests
GENERATOR_DIR = ./tests/generators
DEPOSIT_CONTRACT_COMPILER_DIR = ./deposit_contract/compiler
DEPOSIT_CONTRACT_TESTER_DIR = ./deposit_contract/tester
SOLIDITY_DEPOSIT_CONTRACT_DIR = ./solidity_deposit_contract
SOLIDITY_DEPOSIT_CONTRACT_SOURCE = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/deposit_contract.sol
SOLIDITY_FILE_NAME = deposit_contract.json
DEPOSIT_CONTRACT_TESTER_DIR = ${SOLIDITY_DEPOSIT_CONTRACT_DIR}/web3_tester
CONFIGS_DIR = ./configs
# Collect a list of generator names
@ -25,6 +27,11 @@ COV_INDEX_FILE=$(PY_SPEC_DIR)/$(COV_HTML_OUT)/index.html
CURRENT_DIR = ${CURDIR}
LINTER_CONFIG_FILE = $(CURRENT_DIR)/linter.ini
export DAPP_SKIP_BUILD:=1
export DAPP_SRC:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR)
export DAPP_LIB:=$(SOLIDITY_DEPOSIT_CONTRACT_DIR)/lib
export DAPP_JSON:=build/combined.json
.PHONY: clean partial_clean all test citest lint generate_tests pyspec install_test open_cov \
install_deposit_contract_tester test_deposit_contract install_deposit_contract_compiler \
compile_deposit_contract test_compile_deposit_contract check_toc
@ -38,7 +45,6 @@ partial_clean:
rm -rf .pytest_cache
rm -f .coverage
rm -rf $(PY_SPEC_DIR)/.pytest_cache
rm -rf $(DEPOSIT_CONTRACT_COMPILER_DIR)/.pytest_cache
rm -rf $(DEPOSIT_CONTRACT_TESTER_DIR)/.pytest_cache
rm -rf $(PY_SPEC_DIR)/phase0
rm -rf $(PY_SPEC_DIR)/phase1
@ -46,7 +52,7 @@ partial_clean:
rm -rf $(PY_SPEC_DIR)/.coverage
rm -rf $(PY_SPEC_DIR)/test-reports
rm -rf eth2spec.egg-info dist build
rm -rf build
clean: partial_clean
rm -rf venv
@ -107,24 +113,26 @@ lint: pyspec
flake8 --config $(LINTER_CONFIG_FILE) ./eth2spec \
&& mypy --config-file $(LINTER_CONFIG_FILE) -p eth2spec.phase0 -p eth2spec.phase1
install_deposit_contract_tester:
cd $(DEPOSIT_CONTRACT_TESTER_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt
compile_deposit_contract:
@cd $(SOLIDITY_DEPOSIT_CONTRACT_DIR)
@git submodule update --recursive --init
@solc --metadata-literal --optimize --optimize-runs 5000000 --bin --abi --combined-json=abi,bin,bin-runtime,srcmap,srcmap-runtime,ast,metadata,storage-layout --overwrite -o build $(SOLIDITY_DEPOSIT_CONTRACT_SOURCE) $(SOLIDITY_DEPOSIT_CONTRACT_DIR)/tests/deposit_contract.t.sol
@/bin/echo -n '{"abi": ' > $(SOLIDITY_FILE_NAME)
@cat build/DepositContract.abi >> $(SOLIDITY_FILE_NAME)
@/bin/echo -n ', "bytecode": "0x' >> $(SOLIDITY_FILE_NAME)
@cat build/DepositContract.bin >> $(SOLIDITY_FILE_NAME)
@/bin/echo -n '"}' >> $(SOLIDITY_FILE_NAME)
test_deposit_contract:
dapp test -v --fuzz-runs 5
install_deposit_contract_web3_tester:
cd $(DEPOSIT_CONTRACT_TESTER_DIR); python3 -m venv venv; . venv/bin/activate; pip3 install -r requirements.txt
test_deposit_contract_web3_tests:
cd $(DEPOSIT_CONTRACT_TESTER_DIR); . venv/bin/activate; \
python -m pytest .
install_deposit_contract_compiler:
cd $(DEPOSIT_CONTRACT_COMPILER_DIR); python3.7 -m venv venv; . venv/bin/activate; pip3.7 install -r requirements.txt
compile_deposit_contract:
cd $(DEPOSIT_CONTRACT_COMPILER_DIR); . venv/bin/activate; \
python3.7 deposit_contract/compile.py ../contracts/validator_registration.vy
test_compile_deposit_contract:
cd $(DEPOSIT_CONTRACT_COMPILER_DIR); . venv/bin/activate; \
python3.7 -m pytest .
# Runs a generator, identified by param 1
define run_generator
# Started!

View File

@ -1,6 +1,6 @@
# Ethereum 2.0 Specifications
[![Join the chat at https://discord.gg/hpFs23p](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/hpFs23p) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Join the chat at https://discord.gg/qGpsxSA](https://img.shields.io/badge/chat-on%20discord-blue.svg)](https://discord.gg/hpFs23p) [![Join the chat at https://gitter.im/ethereum/sharding](https://badges.gitter.im/ethereum/sharding.svg)](https://gitter.im/ethereum/sharding?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
To learn more about sharding and Ethereum 2.0 (Serenity), see the [sharding FAQ](https://eth.wiki/sharding/Sharding-FAQs) and the [research compendium](https://notes.ethereum.org/s/H1PGqDhpm).

View File

@ -28,6 +28,8 @@ HYSTERESIS_QUOTIENT: 4
HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5
# 3
PROPORTIONAL_SLASHING_MULTIPLIER: 3
# Fork Choice

View File

@ -27,7 +27,8 @@ HYSTERESIS_QUOTIENT: 4
HYSTERESIS_DOWNWARD_MULTIPLIER: 1
# 5 (plus 1.25)
HYSTERESIS_UPWARD_MULTIPLIER: 5
# 3
PROPORTIONAL_SLASHING_MULTIPLIER: 3
# Fork Choice

View File

@ -1,38 +0,0 @@
# Deposit contract
## How to set up the testing environment?
Under the `eth2.0-specs` directory, execute:
```sh
make install_deposit_contract_tester
```
## How to compile the contract?
```sh
make compile_deposit_contract
```
The compiler dependencies can be installed with:
```sh
make install_deposit_contract_compiler
```
Note that this requires python 3.7 to be installed. The pinned vyper version will not work on 3.8.
The ABI and bytecode will be updated at [`contracts/validator_registration.json`](./contracts/validator_registration.json).
## How to run tests?
For running the contract tests:
```sh
make test_deposit_contract
```
For testing the compiler output against the expected formally-verified bytecode:
```sh
make test_compile_deposit_contract
```

View File

@ -1,31 +0,0 @@
import argparse
import json
import os
from vyper import compiler
DIR = os.path.dirname(__file__)
def generate_compiled_json(file_path: str):
deposit_contract_code = open(file_path).read()
abi = compiler.mk_full_signature(deposit_contract_code)
bytecode = compiler.compile_code(deposit_contract_code)['bytecode']
contract_json = {
'abi': abi,
'bytecode': bytecode,
}
# write json
basename = os.path.basename(file_path)
dirname = os.path.dirname(file_path)
contract_name = basename.split('.')[0]
with open(dirname + "/{}.json".format(contract_name), 'w') as f_write:
json.dump(contract_json, f_write)
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument("path", type=str, help="the path of the contract")
args = parser.parse_args()
path = args.path
generate_compiled_json(path)

View File

@ -1,29 +0,0 @@
from vyper import compiler
import json
import os
DIR = os.path.dirname(__file__)
def get_deposit_contract_code():
file_path = os.path.join(DIR, '../../contracts/validator_registration.vy')
deposit_contract_code = open(file_path).read()
return deposit_contract_code
def get_deposit_contract_json():
file_path = os.path.join(DIR, '../../contracts/validator_registration.json')
deposit_contract_json = open(file_path).read()
return json.loads(deposit_contract_json)
def test_compile_deposit_contract():
compiled_deposit_contract_json = get_deposit_contract_json()
deposit_contract_code = get_deposit_contract_code()
abi = compiler.mk_full_signature(deposit_contract_code)
bytecode = compiler.compile_code(deposit_contract_code)['bytecode']
assert abi == compiled_deposit_contract_json["abi"]
assert bytecode == compiled_deposit_contract_json["bytecode"]

View File

@ -1,7 +0,0 @@
# Vyper beta version used to generate the bytecode that was then formally verified.
# On top of this beta version, a later change was backported, and included in the formal verification:
# https://github.com/vyperlang/vyper/issues/1761
# The resulting vyper version is pinned and maintained as protected branch.
git+https://github.com/vyperlang/vyper@1761-HOTFIX-v0.1.0-beta.13
pytest==3.6.1

View File

@ -1,10 +0,0 @@
from distutils.core import setup
setup(
name='deposit_contract_compiler',
packages=['deposit_contract'],
package_dir={"": "."},
python_requires="3.7", # pinned vyper compiler stops working after 3.7. See vyper issue 1835.
tests_requires=["pytest==3.6.1"],
install_requires=[], # see requirements.txt file
)

File diff suppressed because one or more lines are too long

View File

@ -1,110 +0,0 @@
# Vyper target 0.1.0b13.hotfix1761
MIN_DEPOSIT_AMOUNT: constant(uint256) = 1000000000 # Gwei
DEPOSIT_CONTRACT_TREE_DEPTH: constant(uint256) = 32
MAX_DEPOSIT_COUNT: constant(uint256) = 4294967295 # 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1
PUBKEY_LENGTH: constant(uint256) = 48 # bytes
WITHDRAWAL_CREDENTIALS_LENGTH: constant(uint256) = 32 # bytes
SIGNATURE_LENGTH: constant(uint256) = 96 # bytes
AMOUNT_LENGTH: constant(uint256) = 8 # bytes
DepositEvent: event({
pubkey: bytes[48],
withdrawal_credentials: bytes[32],
amount: bytes[8],
signature: bytes[96],
index: bytes[8],
})
branch: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
deposit_count: uint256
# Compute hashes in empty sparse Merkle tree
zero_hashes: bytes32[DEPOSIT_CONTRACT_TREE_DEPTH]
@public
def __init__():
for i in range(DEPOSIT_CONTRACT_TREE_DEPTH - 1):
self.zero_hashes[i + 1] = sha256(concat(self.zero_hashes[i], self.zero_hashes[i]))
@private
@constant
def to_little_endian_64(value: uint256) -> bytes[8]:
# Reversing bytes using bitwise uint256 manipulations
# Note: array accesses of bytes[] are not currently supported in Vyper
# Note: this function is only called when `value < 2**64`
y: uint256 = 0
x: uint256 = value
for _ in range(8):
y = shift(y, 8)
y = y + bitwise_and(x, 255)
x = shift(x, -8)
return slice(convert(y, bytes32), start=24, len=8)
@public
@constant
def get_deposit_root() -> bytes32:
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
node: bytes32 = zero_bytes32
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
node = sha256(concat(self.branch[height], node))
else:
node = sha256(concat(node, self.zero_hashes[height]))
size /= 2
return sha256(concat(node, self.to_little_endian_64(self.deposit_count), slice(zero_bytes32, start=0, len=24)))
@public
@constant
def get_deposit_count() -> bytes[8]:
return self.to_little_endian_64(self.deposit_count)
@payable
@public
def deposit(pubkey: bytes[PUBKEY_LENGTH],
withdrawal_credentials: bytes[WITHDRAWAL_CREDENTIALS_LENGTH],
signature: bytes[SIGNATURE_LENGTH],
deposit_data_root: bytes32):
# Avoid overflowing the Merkle tree (and prevent edge case in computing `self.branch`)
assert self.deposit_count < MAX_DEPOSIT_COUNT
# Check deposit amount
deposit_amount: uint256 = msg.value / as_wei_value(1, "gwei")
assert deposit_amount >= MIN_DEPOSIT_AMOUNT
# Length checks for safety
assert len(pubkey) == PUBKEY_LENGTH
assert len(withdrawal_credentials) == WITHDRAWAL_CREDENTIALS_LENGTH
assert len(signature) == SIGNATURE_LENGTH
# Emit `DepositEvent` log
amount: bytes[8] = self.to_little_endian_64(deposit_amount)
log.DepositEvent(pubkey, withdrawal_credentials, amount, signature, self.to_little_endian_64(self.deposit_count))
# Compute deposit data root (`DepositData` hash tree root)
zero_bytes32: bytes32 = 0x0000000000000000000000000000000000000000000000000000000000000000
pubkey_root: bytes32 = sha256(concat(pubkey, slice(zero_bytes32, start=0, len=64 - PUBKEY_LENGTH)))
signature_root: bytes32 = sha256(concat(
sha256(slice(signature, start=0, len=64)),
sha256(concat(slice(signature, start=64, len=SIGNATURE_LENGTH - 64), zero_bytes32)),
))
node: bytes32 = sha256(concat(
sha256(concat(pubkey_root, withdrawal_credentials)),
sha256(concat(amount, slice(zero_bytes32, start=0, len=32 - AMOUNT_LENGTH), signature_root)),
))
# Verify computed and expected deposit data roots match
assert node == deposit_data_root
# Add deposit data root to Merkle tree (update a single `branch` node)
self.deposit_count += 1
size: uint256 = self.deposit_count
for height in range(DEPOSIT_CONTRACT_TREE_DEPTH):
if bitwise_and(size, 1) == 1: # More gas efficient than `size % 2 == 1`
self.branch[height] = node
break
node = sha256(concat(self.branch[height], node))
size /= 2

View File

@ -156,8 +156,10 @@ SSZObject = TypeVar('SSZObject', bound=View)
CONFIG_NAME = 'mainnet'
'''
SUNDRY_CONSTANTS_FUNCTIONS = '''
def ceillog2(x: uint64) -> int:
return (x - 1).bit_length()
def ceillog2(x: int) -> uint64:
if x < 1:
raise ValueError(f"ceillog2 accepts only positive values, x={x}")
return uint64((x - 1).bit_length())
'''
PHASE0_SUNDRY_FUNCTIONS = '''
def get_eth1_data(block: Eth1Block) -> Eth1Data:

View File

@ -0,0 +1,28 @@
The contract logic here is based on the deposit contract written in Vyper.
The original repository (https://github.com/ethereum/deposit_contract) had the following authors:
@NIC619
@carver
@hwwhww
@davesque
@ralexstokes
@ChihChengLiang
@nisdas
@djrtwo
@JustinDrake
@vbuterin
@njgheorghita
@CarlBeek
In eth2.0-specs repository (https://github.com/ethereum/eth2.0-specs), its current location, it had the following additional authors:
@daejunpark
And this repository has the following authors:
@axic
@MrChico
@chriseth
(All of the above are GitHub usernames in order of they appeared first in the commit history.)

View File

@ -0,0 +1,25 @@
# Deposit Contract
## History
This is a rewrite of the [Vyper Eth 2.0 deposit contract](https://github.com/ethereum/eth2.0-specs/blob/v0.12.2/deposit_contract/contracts/validator_registration.vy) to Solidity.
The original motivation was to run the SMTChecker and the new Yul IR generator option (`--ir`) in the compiler.
As of June 2020, version `r1` of the Solidity deposit contract has been verified and is considered for adoption.
See this [blog post](https://blog.ethereum.org/2020/06/23/eth2-quick-update-no-12/) for more information.
In August 2020, version `r2` was released with metadata modifications and relicensed to CC0-1.0. Afterward, this contract has been ported back to from [`axic/eth2-deposit-contract`](https://github.com/axic/eth2-deposit-contract) to this repository and replaced the Vyper deposit contract.
## Running web3 tests
1. In the `eth2.0-specs` directory run `make install_deposit_contract_web3_tester` to install the tools needed (make sure to have Python 3.7 and pip installed).
2. In the `eth2.0-specs` directory run `make test_deposit_contract_web3_tests` to execute the tests.
## Running randomized `dapp` tests:
Install the latest version of `dapp` by following the instructions at [dapp.tools](https://dapp.tools/). Then in the `eth2.0-specs` directory run:
```sh
make test_deposit_contract
```

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,178 @@
//
//
//
//
//
//
//
//
// SPDX-License-Identifier: CC0-1.0
pragma solidity 0.6.11;
// This interface is designed to be compatible with the Vyper version.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
interface IDepositContract {
/// @notice A processed deposit event.
event DepositEvent(
bytes pubkey,
bytes withdrawal_credentials,
bytes amount,
bytes signature,
bytes index
);
/// @notice Submit a Phase 0 DepositData object.
/// @param pubkey A BLS12-381 public key.
/// @param withdrawal_credentials Commitment to a public key for withdrawals.
/// @param signature A BLS12-381 signature.
/// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object.
/// Used as a protection against malformed input.
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable;
/// @notice Query the current deposit root hash.
/// @return The deposit root hash.
function get_deposit_root() external view returns (bytes32);
/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view returns (bytes memory);
}
// Based on official specification in https://eips.ethereum.org/EIPS/eip-165
interface ERC165 {
/// @notice Query if a contract implements an interface
/// @param interfaceId The interface identifier, as specified in ERC-165
/// @dev Interface identification is specified in ERC-165. This function
/// uses less than 30,000 gas.
/// @return `true` if the contract implements `interfaceId` and
/// `interfaceId` is not 0xffffffff, `false` otherwise
function supportsInterface(bytes4 interfaceId) external pure returns (bool);
}
// This is a rewrite of the Vyper Eth2.0 deposit contract in Solidity.
// It tries to stay as close as possible to the original source code.
/// @notice This is the Ethereum 2.0 deposit contract interface.
/// For more information see the Phase 0 specification under https://github.com/ethereum/eth2.0-specs
contract DepositContract is IDepositContract, ERC165 {
uint constant DEPOSIT_CONTRACT_TREE_DEPTH = 32;
// NOTE: this also ensures `deposit_count` will fit into 64-bits
uint constant MAX_DEPOSIT_COUNT = 2**DEPOSIT_CONTRACT_TREE_DEPTH - 1;
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] branch;
uint256 deposit_count;
bytes32[DEPOSIT_CONTRACT_TREE_DEPTH] zero_hashes;
constructor() public {
// Compute hashes in empty sparse Merkle tree
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH - 1; height++)
zero_hashes[height + 1] = sha256(abi.encodePacked(zero_hashes[height], zero_hashes[height]));
}
function get_deposit_root() override external view returns (bytes32) {
bytes32 node;
uint size = deposit_count;
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
if ((size & 1) == 1)
node = sha256(abi.encodePacked(branch[height], node));
else
node = sha256(abi.encodePacked(node, zero_hashes[height]));
size /= 2;
}
return sha256(abi.encodePacked(
node,
to_little_endian_64(uint64(deposit_count)),
bytes24(0)
));
}
function get_deposit_count() override external view returns (bytes memory) {
return to_little_endian_64(uint64(deposit_count));
}
function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) override external payable {
// Extended ABI length checks since dynamic types are used.
require(pubkey.length == 48, "DepositContract: invalid pubkey length");
require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length");
require(signature.length == 96, "DepositContract: invalid signature length");
// Check deposit amount
require(msg.value >= 1 ether, "DepositContract: deposit value too low");
require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei");
uint deposit_amount = msg.value / 1 gwei;
require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high");
// Emit `DepositEvent` log
bytes memory amount = to_little_endian_64(uint64(deposit_amount));
emit DepositEvent(
pubkey,
withdrawal_credentials,
amount,
signature,
to_little_endian_64(uint64(deposit_count))
);
// Compute deposit data root (`DepositData` hash tree root)
bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, bytes16(0)));
bytes32 signature_root = sha256(abi.encodePacked(
sha256(abi.encodePacked(signature[:64])),
sha256(abi.encodePacked(signature[64:], bytes32(0)))
));
bytes32 node = sha256(abi.encodePacked(
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
sha256(abi.encodePacked(amount, bytes24(0), signature_root))
));
// Verify computed and expected deposit data roots match
require(node == deposit_data_root, "DepositContract: reconstructed DepositData does not match supplied deposit_data_root");
// Avoid overflowing the Merkle tree (and prevent edge case in computing `branch`)
require(deposit_count < MAX_DEPOSIT_COUNT, "DepositContract: merkle tree full");
// Add deposit data root to Merkle tree (update a single `branch` node)
deposit_count += 1;
uint size = deposit_count;
for (uint height = 0; height < DEPOSIT_CONTRACT_TREE_DEPTH; height++) {
if ((size & 1) == 1) {
branch[height] = node;
return;
}
node = sha256(abi.encodePacked(branch[height], node));
size /= 2;
}
// As the loop should always end prematurely with the `return` statement,
// this code should be unreachable. We assert `false` just to be safe.
assert(false);
}
function supportsInterface(bytes4 interfaceId) override external pure returns (bool) {
return interfaceId == type(ERC165).interfaceId || interfaceId == type(IDepositContract).interfaceId;
}
function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {
ret = new bytes(8);
bytes8 bytesValue = bytes8(value);
// Byteswapping during copying to bytes.
ret[0] = bytesValue[7];
ret[1] = bytesValue[6];
ret[2] = bytesValue[5];
ret[3] = bytesValue[4];
ret[4] = bytesValue[3];
ret[5] = bytesValue[2];
ret[6] = bytesValue[1];
ret[7] = bytesValue[0];
}
}

@ -0,0 +1 @@
Subproject commit eb7148d43c1ca6f9890361e2e2378364af2430ba

View File

@ -0,0 +1,18 @@
let dapptools = builtins.fetchGit {
url = "https://github.com/dapphub/dapptools.git";
rev = "11dcefe1f03b0acafe76b4d7d54821ef6bd63131";
};
nixpkgs = builtins.fetchGit {
url = "https://github.com/nixos/nixpkgs";
ref = "release-19.03";
rev = "f1707d8875276cfa110139435a7e8998b4c2a4fd";
};
pkgs-for-dapp = import nixpkgs {
overlays = [
(import (dapptools + /overlay.nix))
];
};
in
pkgs-for-dapp.mkShell {
buildInputs = [ pkgs-for-dapp.dapp pkgs-for-dapp.solc pkgs-for-dapp.hevm ];
}

View File

@ -0,0 +1,142 @@
pragma solidity ^0.6.2;
import "../lib/ds-test/src/test.sol";
import "./vyper_setup.sol";
import "../deposit_contract.sol";
contract DepositContractTest is DSTest {
DepositContract depositContract_sol;
DepositContract depositContract_vyp;
uint64 constant GWEI = 1000000000;
function setUp() public {
VyperSetup vyperSetup = new VyperSetup();
depositContract_vyp = DepositContract(vyperSetup.deployDeposit());
depositContract_sol = new DepositContract();
}
// --- SUCCESS TESTS ---
// Tests initialized storage values, comparing vyper and solidity
function test_empty_root() public {
bytes32 zHash = 0x0000000000000000000000000000000000000000000000000000000000000000;
bytes32 zHashN = zHash;
for (uint i = 0; i <= 31; i++) {
zHashN = sha256(abi.encodePacked(zHashN, zHashN));
}
assertEq(sha256(abi.encodePacked(zHashN, zHash)), depositContract_vyp.get_deposit_root());
assertEq(depositContract_sol.get_deposit_root(), depositContract_vyp.get_deposit_root());
}
// Generates 16 random deposits, insert them in both vyper and solidity version and compare get_deposit_root after each insertion
function test_16_deposits(bytes32[16] memory pubkey_one, bytes16[16] memory pubkey_two, bytes32[16] memory _withdrawal_credentials,
bytes32[16] memory sig_one, bytes32[16] memory sig_two, bytes32[16] memory sig_three, uint32[16] memory amount) public {
uint j;
for (uint i = 0; i < 16; i++) {
// as of dcaa774, the solidity version is more restrictive than vyper and requires deposits to be divisible by GWEI
uint64 gweiamount = amount[i] * GWEI;
if (1 ether <= gweiamount) {
j++;
deposit_in(depositContract_sol, pubkey_one[i], pubkey_two[i], _withdrawal_credentials[i], sig_one[i], sig_two[i], sig_three[i], gweiamount);
deposit_in(depositContract_vyp, pubkey_one[i], pubkey_two[i], _withdrawal_credentials[i], sig_one[i], sig_two[i], sig_three[i], gweiamount);
assertEq(depositContract_sol.get_deposit_root(), depositContract_vyp.get_deposit_root());
assertEq(keccak256(abi.encodePacked(depositContract_sol.get_deposit_count())), keccak256(abi.encodePacked(depositContract_vyp.get_deposit_count())));
assertEq(keccak256(abi.encodePacked(depositContract_sol.get_deposit_count())), keccak256(to_little_endian_64(uint64(j))));
}
}
}
// The solidity contract fails when given a deposit which is not divisible by GWEI
function testFail_deposit_not_divisible_by_gwei(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials,
bytes32 sig_one, bytes32 sig_two, bytes32 sig_three) public {
deposit_in(depositContract_sol, pubkey_one, pubkey_two, _withdrawal_credentials, sig_one, sig_two, sig_three, 1 ether + 1);
}
// --- FAILURE TESTS ---
// if the node is given randomly instead of as the ssz root, the chances of success are so unlikely that we can assert it to be false
function testFail_malformed_node_vyp(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one,
bytes32 sig_two, bytes32 sig_three, uint64 amount, bytes32 node) public {
bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two);
bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials); //I wish just recasting to `bytes` would work..
bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three);
depositContract_vyp.deposit{value: amount}(pubkey, withdrawal_credentials, signature, node);
}
// If the node is taken randomly instead of as the ssz root, the chances of success are so unlikely that we can assert it to be false
function testFail_malformed_node_sol(bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one,
bytes32 sig_two, bytes32 sig_three, uint64 amount, bytes32 node) public {
bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two);
bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials);
bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three);
depositContract_sol.deposit{value: amount}(pubkey, withdrawal_credentials, signature, node);
}
// if bytes lengths are wrong, the call will fail
function testFail_malformed_calldata_vyp(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, uint64 amount) public {
if (amount >= 1000000000000000000) {
if (!(pubkey.length == 48 && withdrawal_credentials.length == 32 && signature.length == 96)) {
depositContract_vyp.deposit{value: amount}(pubkey, withdrawal_credentials, signature,
encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI))
);
} else { revert(); }
} else { revert(); }
}
function testFail_malformed_calldata_sol(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, uint64 amount) public {
if (amount >= 1000000000000000000) {
if (!(pubkey.length == 48 && withdrawal_credentials.length == 32 && signature.length == 96)) {
depositContract_sol.deposit{value: amount}(pubkey, withdrawal_credentials, signature,
encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI))
);
} else { revert(); }
} else { revert(); }
}
// --- HELPER FUNCTIONS ---
function deposit_in(DepositContract depositContract, bytes32 pubkey_one, bytes16 pubkey_two, bytes32 _withdrawal_credentials, bytes32 sig_one, bytes32 sig_two, bytes32 sig_three, uint64 amount) public {
bytes memory pubkey = abi.encodePacked(pubkey_one, pubkey_two);
bytes memory withdrawal_credentials = abi.encodePacked(_withdrawal_credentials);
bytes memory signature = abi.encodePacked(sig_one, sig_two, sig_three);
bytes32 node = encode_node(pubkey, withdrawal_credentials, signature, to_little_endian_64(amount / GWEI));
depositContract.deposit{value: amount}(pubkey, withdrawal_credentials, signature, node);
}
function slice(bytes memory a, uint32 offset, uint32 size) pure internal returns (bytes memory result) {
result = new bytes(size);
for (uint i = 0; i < size; i++) {
result[i] = a[offset + i];
}
}
function encode_node(bytes memory pubkey, bytes memory withdrawal_credentials, bytes memory signature, bytes memory amount) public pure returns (bytes32) {
bytes16 zero_bytes16;
bytes24 zero_bytes24;
bytes32 zero_bytes32;
bytes32 pubkey_root = sha256(abi.encodePacked(pubkey, zero_bytes16));
bytes32 signature_root = sha256(abi.encodePacked(
sha256(abi.encodePacked(slice(signature, 0, 64))),
sha256(abi.encodePacked(slice(signature, 64, 32), zero_bytes32))
));
return sha256(abi.encodePacked(
sha256(abi.encodePacked(pubkey_root, withdrawal_credentials)),
sha256(abi.encodePacked(amount, zero_bytes24, signature_root))
));
}
function to_little_endian_64(uint64 value) internal pure returns (bytes memory ret) {
ret = new bytes(8);
ret[0] = bytes1(uint8(value & 0xff));
ret[1] = bytes1(uint8((value >> 8) & 0xff));
ret[2] = bytes1(uint8((value >> 16) & 0xff));
ret[3] = bytes1(uint8((value >> 24) & 0xff));
ret[4] = bytes1(uint8((value >> 32) & 0xff));
ret[5] = bytes1(uint8((value >> 40) & 0xff));
ret[6] = bytes1(uint8((value >> 48) & 0xff));
ret[7] = bytes1(uint8((value >> 56) & 0xff));
}
}

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ DIR = os.path.dirname(__file__)
def get_deposit_contract_json():
file_path = os.path.join(DIR, '../../contracts/validator_registration.json')
file_path = os.path.join(DIR, '../../deposit_contract.json')
deposit_contract_json = open(file_path).read()
return json.loads(deposit_contract_json)

View File

@ -6,7 +6,7 @@ from eth2spec.phase0.spec import DepositData
from eth2spec.utils.ssz.ssz_typing import List
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
from deposit_contract.conftest import (
from tests.conftest import (
FULL_DEPOSIT_AMOUNT,
MIN_DEPOSIT_AMOUNT,
)

View File

@ -195,6 +195,7 @@ The following values are (non-configurable) constants used throughout the specif
| `HYSTERESIS_QUOTIENT` | `uint64(4)` |
| `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` |
| `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.)
@ -1554,10 +1555,11 @@ def process_registry_updates(state: BeaconState) -> None:
def process_slashings(state: BeaconState) -> None:
epoch = get_current_epoch(state)
total_balance = get_total_active_balance(state)
adjusted_total_slashing_balance = min(sum(state.slashings) * PROPORTIONAL_SLASHING_MULTIPLIER, total_balance)
for index, validator in enumerate(state.validators):
if validator.slashed and epoch + EPOCHS_PER_SLASHINGS_VECTOR // 2 == validator.withdrawable_epoch:
increment = EFFECTIVE_BALANCE_INCREMENT # Factored out from penalty numerator to avoid uint64 overflow
penalty_numerator = validator.effective_balance // increment * min(sum(state.slashings) * 3, total_balance)
penalty_numerator = validator.effective_balance // increment * adjusted_total_slashing_balance
penalty = penalty_numerator // total_balance * increment
decrease_balance(state, ValidatorIndex(index), penalty)
```

View File

@ -16,7 +16,7 @@
- [Deposit amount](#deposit-amount)
- [Withdrawal credentials](#withdrawal-credentials)
- [`DepositEvent` log](#depositevent-log)
- [Vyper code](#vyper-code)
- [Solidity code](#solidity-code)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
<!-- /TOC -->
@ -53,7 +53,7 @@ _Note_: See [here](https://chainid.network/) for a comprehensive list of public
### `deposit` function
The deposit contract has a public `deposit` function to make deposits. It takes as arguments `pubkey: bytes[48], withdrawal_credentials: bytes[32], signature: bytes[96], deposit_data_root: bytes32`. The first three arguments populate a [`DepositData`](./beacon-chain.md#depositdata) object, and `deposit_data_root` is the expected `DepositData` root as a protection against malformatted calldata.
The deposit contract has a public `deposit` function to make deposits. It takes as arguments `bytes calldata pubkey, bytes calldata withdrawal_credentials, bytes calldata signature, bytes32 deposit_data_root`. The first three arguments populate a [`DepositData`](./beacon-chain.md#depositdata) object, and `deposit_data_root` is the expected `DepositData` root as a protection against malformatted calldata.
#### Deposit amount
@ -72,8 +72,8 @@ The private key corresponding to `withdrawal_pubkey` will be required to initiat
Every Ethereum 1.0 deposit emits a `DepositEvent` log for consumption by the beacon chain. The deposit contract does little validation, pushing most of the validator onboarding logic to the beacon chain. In particular, the proof of possession (a BLS12-381 signature) is not verified by the deposit contract.
## Vyper code
## Solidity code
The deposit contract source code, written in Vyper, is available [here](../../deposit_contract/contracts/validator_registration.vy).
The deposit contract source code, written in Solidity, is available [here](../../solidity_deposit_contract/deposit_contract.sol).
*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

@ -391,8 +391,10 @@ The following validations MUST pass before forwarding the `attestation` on the s
(within a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) --
i.e. `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`
(a client MAY queue future attestations for processing at the appropriate slot).
- _[REJECT]_ The attestation's epoch matches its target -- i.e. `attestation.data.target.epoch ==
compute_epoch_at_slot(attestation.data.slot)`
- _[REJECT]_ The attestation is unaggregated --
that is, it has exactly one participating validator (`len([bit in bit attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set).
that is, it has exactly one participating validator (`len([bit for bit in attestation.aggregation_bits if bit]) == 1`, i.e. exactly 1 bit is set).
- _[IGNORE]_ There has been no other valid attestation seen on an attestation subnet
that has an identical `attestation.data.target.epoch` and participating validator index.
- _[REJECT]_ The signature of `attestation` is valid.
@ -400,12 +402,15 @@ The following validations MUST pass before forwarding the `attestation` on the s
(via both gossip and non-gossip sources)
(a client MAY queue aggregates for processing once block is retrieved).
- _[REJECT]_ The block being voted for (`attestation.data.beacon_block_root`) passes validation.
- _[REJECT]_ The attestation's target block is an ancestor of the block named in the LMD vote -- i.e.
`get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(attestation.data.target.epoch)) == attestation.data.target.root`
- _[REJECT]_ The current `finalized_checkpoint` is an ancestor of the `block` defined by `attestation.data.beacon_block_root` -- i.e.
`get_ancestor(store, attestation.data.beacon_block_root, compute_start_slot_at_epoch(store.finalized_checkpoint.epoch))
== store.finalized_checkpoint.root`
#### Attestations and Aggregation
Attestation broadcasting is grouped into subnets defined by a topic.
@ -723,7 +728,7 @@ The request MUST be encoded as an SSZ-container.
The response MUST consist of zero or more `response_chunk`.
Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload.
Clients MUST keep a record of signed blocks seen since the since the start of the weak subjectivity period
Clients MUST keep a record of signed blocks seen since the start of the weak subjectivity period
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.

View File

@ -194,9 +194,14 @@ def is_proposer(state: BeaconState, validator_index: ValidatorIndex) -> bool:
### Lookahead
The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot. Note that this lookahead does not apply to proposing, which must be checked during the epoch in question.
The beacon chain shufflings are designed to provide a minimum of 1 epoch lookahead
on the validator's upcoming committee assignments for attesting dictated by the shuffling and slot.
Note that this lookahead does not apply to proposing, which must be checked during the epoch in question.
`get_committee_assignment` should be called at the start of each epoch to get the assignment for the next epoch (`current_epoch + 1`). A validator should plan for future assignments by noting at which future slot they will have to attest and joining the committee index attestation subnet related to their committee assignment.
`get_committee_assignment` should be called at the start of each epoch
to get the assignment for the next epoch (`current_epoch + 1`).
A validator should plan for future assignments by noting their assigned attestation
slot and joining the committee index attestation subnet related to their committee assignment.
Specifically a validator should:
* Call `get_committee_assignment(state, next_epoch, validator_index)` when checking for next epoch assignments.
@ -214,9 +219,18 @@ A validator has two primary responsibilities to the beacon chain: [proposing blo
### Block proposal
A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) at the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`. To propose, the validator selects the `BeaconBlock`, `parent`, that in their view of the fork choice is the head of the chain during `slot - 1`. The validator creates, signs, and broadcasts a `block` that is a child of `parent` that satisfies a valid [beacon chain state transition](./beacon-chain.md#beacon-chain-state-transition-function).
A validator is expected to propose a [`SignedBeaconBlock`](./beacon-chain.md#signedbeaconblock) at
the beginning of any slot during which `is_proposer(state, validator_index)` returns `True`.
To propose, the validator selects the `BeaconBlock`, `parent`,
that in their view of the fork choice is the head of the chain during `slot - 1`.
The validator creates, signs, and broadcasts a `block` that is a child of `parent`
that satisfies a valid [beacon chain state transition](./beacon-chain.md#beacon-chain-state-transition-function).
There is one proposer per slot, so if there are N active validators any individual validator will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~6 weeks).
There is one proposer per slot, so if there are N active validators any individual validator
will on average be assigned to propose once per N slots (e.g. at 312,500 validators = 10 million ETH, that's once per ~6 weeks).
*Note*: In this section, `state` is the state of the slot for the block proposal _without_ the block yet applied.
That is, `state` is the `previous_state` processed through any empty slots up to the assigned slot using `process_slots(previous_state, slot)`.
#### Preparing for a `BeaconBlock`
@ -251,7 +265,13 @@ def get_epoch_signature(state: BeaconState, block: BeaconBlock, privkey: int) ->
##### 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 immediately allowing new deposits to be processed. 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`.
###### `Eth1Block`

View File

@ -46,6 +46,7 @@
- [`compute_updated_gasprice`](#compute_updated_gasprice)
- [`compute_committee_source_epoch`](#compute_committee_source_epoch)
- [Beacon state accessors](#beacon-state-accessors)
- [Updated `get_committee_count_per_slot`](#updated-get_committee_count_per_slot)
- [`get_active_shard_count`](#get_active_shard_count)
- [`get_online_validator_indices`](#get_online_validator_indices)
- [`get_shard_committee`](#get_shard_committee)
@ -105,6 +106,7 @@ Configuration is not namespaced. Instead it is strictly an extension;
| Name | Value |
| - | - |
| `MAX_SHARDS` | `2**10` (= 1024) |
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |
| `LIGHT_CLIENT_COMMITTEE_SIZE` | `2**7` (= 128) |
| `GASPRICE_ADJUSTMENT_COEFFICIENT` | `2**3` (= 8) |
@ -510,11 +512,28 @@ def compute_committee_source_epoch(epoch: Epoch, period: uint64) -> Epoch:
### Beacon state accessors
#### Updated `get_committee_count_per_slot`
```python
def get_committee_count_per_slot(state: BeaconState, epoch: Epoch) -> uint64:
"""
Return the number of committees in each slot for the given ``epoch``.
"""
return max(uint64(1), min(
get_active_shard_count(state),
uint64(len(get_active_validator_indices(state, epoch))) // SLOTS_PER_EPOCH // TARGET_COMMITTEE_SIZE,
))
```
#### `get_active_shard_count`
```python
def get_active_shard_count(state: BeaconState) -> uint64:
return len(state.shard_states) # May adapt in the future, or change over time.
"""
Return the number of active shards.
Note that this puts an upper bound on the number of committees per slot.
"""
return INITIAL_ACTIVE_SHARDS
```
#### `get_online_validator_indices`
@ -535,12 +554,11 @@ def get_shard_committee(beacon_state: BeaconState, epoch: Epoch, shard: Shard) -
source_epoch = compute_committee_source_epoch(epoch, SHARD_COMMITTEE_PERIOD)
active_validator_indices = get_active_validator_indices(beacon_state, source_epoch)
seed = get_seed(beacon_state, source_epoch, DOMAIN_SHARD_COMMITTEE)
active_shard_count = get_active_shard_count(beacon_state)
return compute_committee(
indices=active_validator_indices,
seed=seed,
index=shard,
count=active_shard_count,
count=get_active_shard_count(beacon_state),
)
```
@ -607,10 +625,11 @@ def get_start_shard(state: BeaconState, slot: Slot) -> Shard:
else:
# Previous epoch
shard_delta = get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot)
max_committees_per_epoch = MAX_COMMITTEES_PER_SLOT * SLOTS_PER_EPOCH
max_committees_per_slot = active_shard_count
max_committees_in_span = max_committees_per_slot * (current_epoch_start_slot - slot)
return Shard(
# Ensure positive
(state.current_epoch_start_shard + max_committees_per_epoch * active_shard_count - shard_delta)
(state.current_epoch_start_shard + max_committees_in_span - shard_delta)
% active_shard_count
)
```
@ -742,7 +761,6 @@ def process_operations(state: BeaconState, body: BeaconBlockBody) -> None:
def validate_attestation(state: BeaconState, attestation: Attestation) -> None:
data = attestation.data
assert data.index < get_committee_count_per_slot(state, data.target.epoch)
assert data.index < get_active_shard_count(state)
assert data.target.epoch in (get_previous_epoch(state), get_current_epoch(state))
assert data.target.epoch == compute_epoch_at_slot(data.slot)
assert data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= state.slot <= data.slot + SLOTS_PER_EPOCH

View File

@ -36,7 +36,6 @@ Warning: this configuration is not definitive.
| - | - |
| `PHASE_1_FORK_VERSION` | `Version('0x01000000')` |
| `PHASE_1_FORK_SLOT` | `Slot(0)` **TBD** |
| `INITIAL_ACTIVE_SHARDS` | `2**6` (= 64) |
## Fork to Phase 1

View File

@ -165,7 +165,7 @@ variable_lengths = [len(part) for part in variable_parts]
assert sum(fixed_lengths + variable_lengths) < 2**(BYTES_PER_LENGTH_OFFSET * BITS_PER_BYTE)
# Interleave offsets of variable-size parts with fixed-size parts
variable_offsets = [serialize(sum(fixed_lengths + variable_lengths[:i])) for i in range(len(value))]
variable_offsets = [serialize(uint32(sum(fixed_lengths + variable_lengths[:i]))) for i in range(len(value))]
fixed_parts = [part if part != None else variable_offsets[i] for i, part in enumerate(fixed_parts)]
# Return the concatenation of the fixed-size parts (offsets interleaved) with the variable-size parts

View File

@ -31,7 +31,7 @@ def pytest_addoption(parser):
help="config: make the pyspec use the specified configuration"
)
parser.addoption(
"--disable-bls", action="store_true",
"--disable-bls", action="store_true", default=False,
help="bls-default: make tests that are not dependent on BLS run without BLS"
)
parser.addoption(

View File

@ -158,6 +158,15 @@ def low_single_balance(spec):
return [1]
def large_validator_set(spec):
"""
Helper method to create a series of default balances.
Usage: `@with_custom_state(balances_fn=default_balances, ...)`
"""
num_validators = 2 * spec.SLOTS_PER_EPOCH * spec.MAX_COMMITTEES_PER_SLOT * spec.TARGET_COMMITTEE_SIZE
return [spec.MAX_EFFECTIVE_BALANCE] * num_validators
def single_phase(fn):
"""
Decorator that filters out the phases data.

View File

@ -22,8 +22,12 @@ from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_comm
from eth2spec.test.context import (
PHASE0, PHASE1,
spec_state_test, with_all_phases, expect_assertion_error, always_bls, with_phases, dump_skipping_message,
spec_test, spec_state_test, dump_skipping_message,
with_phases, with_all_phases, single_phase,
expect_assertion_error, always_bls,
disable_process_reveal_deadlines,
with_custom_state,
large_validator_set,
)
@ -71,6 +75,7 @@ def test_same_slot_block_transition(spec, state):
def test_empty_block_transition(spec, state):
pre_slot = state.slot
pre_eth1_votes = len(state.eth1_data_votes)
pre_mix = spec.get_randao_mix(state, spec.get_current_epoch(state))
yield 'pre', state
@ -83,7 +88,30 @@ def test_empty_block_transition(spec, state):
assert len(state.eth1_data_votes) == pre_eth1_votes + 1
assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != spec.Bytes32()
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != pre_mix
@with_all_phases
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.EJECTION_BALANCE)
@single_phase
def test_empty_block_transition_large_validator_set(spec, state):
pre_slot = state.slot
pre_eth1_votes = len(state.eth1_data_votes)
pre_mix = spec.get_randao_mix(state, spec.get_current_epoch(state))
yield 'pre', state
block = build_empty_block_for_next_slot(spec, state)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert len(state.eth1_data_votes) == pre_eth1_votes + 1
assert spec.get_block_root_at_slot(state, pre_slot) == signed_block.message.parent_root
assert spec.get_randao_mix(state, spec.get_current_epoch(state)) != pre_mix
def process_and_sign_block_without_header_validations(spec, state, block):
@ -289,6 +317,26 @@ def test_empty_epoch_transition(spec, state):
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
@with_all_phases
@spec_test
@with_custom_state(balances_fn=large_validator_set, threshold_fn=lambda spec: spec.EJECTION_BALANCE)
@single_phase
def test_empty_epoch_transition_large_validator_set(spec, state):
pre_slot = state.slot
yield 'pre', state
block = build_empty_block(spec, state, state.slot + spec.SLOTS_PER_EPOCH)
signed_block = state_transition_and_sign_block(spec, state, block)
yield 'blocks', [signed_block]
yield 'post', state
assert state.slot == block.slot
for slot in range(pre_slot, state.slot):
assert spec.get_block_root_at_slot(state, slot) == block.parent_root
@with_all_phases
@spec_state_test
def test_empty_epoch_transition_not_finalizing(spec, state):

View File

@ -74,3 +74,16 @@ def test_get_start_shard_previous_slot(spec, state):
- spec.get_committee_count_delta(state, start_slot=slot, stop_slot=current_epoch_start_slot)
) % active_shard_count
assert start_shard == expected_start_shard
@with_all_phases_except([PHASE0])
@spec_state_test
def test_get_start_shard_far_past_epoch(spec, state):
initial_epoch = spec.get_current_epoch(state)
initial_start_slot = spec.compute_start_slot_at_epoch(initial_epoch)
initial_start_shard = state.current_epoch_start_shard
for _ in range(spec.MAX_SHARDS + 2):
next_epoch(spec, state)
assert spec.get_start_shard(state, initial_start_slot) == initial_start_shard