From 3cb15806bebadc721ec61fd43cd1786a5cb18d35 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Fri, 11 Sep 2020 17:08:37 -0700 Subject: [PATCH 01/48] Update p2p-interface.md Typo fixes --- specs/phase0/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 1ecbc4ec9..b337be67e 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -725,7 +725,7 @@ A request MUST NOT have a 0 slot increment, i.e. `step >= 1`. `BeaconBlocksByRange` is primarily used to sync historical blocks. -The request MUST be encoded as an SSZ-container. +The request MUST be encoded as a SSZ-container. The response MUST consist of zero or more `response_chunk`. Each _successful_ `response_chunk` MUST contain a single `SignedBeaconBlock` payload. @@ -735,7 +735,7 @@ and MUST support serving requests of blocks up to their own `head_block_root`. Clients MUST respond with at least the first block that exists in the range, if they have it, and no more than `MAX_REQUEST_BLOCKS` blocks. -The following blocks, where they exist, MUST be send in consecutive order. +The following blocks, where they exist, MUST be sent in consecutive order. Clients MAY limit the number of blocks in the response. From 8ad08654243433951c4fb2e1b5e79dc8ef193ba5 Mon Sep 17 00:00:00 2001 From: protolambda Date: Tue, 15 Sep 2020 01:15:16 +0200 Subject: [PATCH 02/48] Use gossip signing policy in p2p spec, see libp2p/specs#294 --- specs/phase0/p2p-interface.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index e90a91341..c38fd6bf0 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -78,6 +78,7 @@ It consists of four main sections: - [How do we upgrade gossip channels (e.g. changes in encoding, compression)?](#how-do-we-upgrade-gossip-channels-eg-changes-in-encoding-compression) - [Why must all clients use the same gossip topic instead of one negotiated between each peer pair?](#why-must-all-clients-use-the-same-gossip-topic-instead-of-one-negotiated-between-each-peer-pair) - [Why are the topics strings and not hashes?](#why-are-the-topics-strings-and-not-hashes) + - [Why are we using the `StrictNoSign` signature policy?](#why-are-we-using-the-strictnosign-signature-policy) - [Why are we overriding the default libp2p pubsub `message-id`?](#why-are-we-overriding-the-default-libp2p-pubsub-message-id) - [Why are these specific gossip parameters chosen?](#why-are-these-specific-gossip-parameters-chosen) - [Why is there `MAXIMUM_GOSSIP_CLOCK_DISPARITY` when validating slot ranges of messages in gossip subnets?](#why-is-there-maximum_gossip_clock_disparity-when-validating-slot-ranges-of-messages-in-gossip-subnets) @@ -243,6 +244,10 @@ Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/ Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. +The optional `from` (1), `seqno` (3), `signature` (5) and `key` (6) protobuf fields are omitted from the message, +since messages are identified by content, anonymous, and signed where necessary in the application layer. +Starting from Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign` signature policy. + The `message-id` of a gossipsub message MUST be the first 8 bytes of the SHA-256 hash of the message data, i.e.: ```python @@ -1164,10 +1169,16 @@ since the domain is finite anyway, and calculating a digest's preimage would be Furthermore, the Eth2 topic names are shorter than their digest equivalents (assuming SHA-256 hash), so hashing topics would bloat messages unnecessarily. +### Why are we using the `StrictNoSign` signature policy? + +The policy omits the `from` (1), `seqno` (3), `signature` (5) and `key` (6) fields. These fields would: +- Expose origin of sender (`from`), type of sender (based on `seqno`) +- Add extra unused data to the gossip, since message IDs are based on `data`, not on the `from` and `seqno`. +- Introduce more message validation than necessary, e.g. no `signature`. + ### Why are we overriding the default libp2p pubsub `message-id`? -For our current purposes, there is no need to address messages based on source peer, -and it seems likely we might even override the message `from` to obfuscate the peer. +For our current purposes, there is no need to address messages based on source peer, or track a message `seqno`. By overriding the default `message-id` to use content-addressing we can filter unnecessary duplicates before hitting the application layer. Some examples of where messages could be duplicated: From 104b83aab3086daeb6afe724bc2d9c0f1e5f00c9 Mon Sep 17 00:00:00 2001 From: ericsson Date: Tue, 22 Sep 2020 18:54:44 +0300 Subject: [PATCH 03/48] use integer division (//) instead of non-integer (/) --- specs/phase0/weak-subjectivity.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/weak-subjectivity.md b/specs/phase0/weak-subjectivity.md index 07c3083a3..7417053f5 100644 --- a/specs/phase0/weak-subjectivity.md +++ b/specs/phase0/weak-subjectivity.md @@ -69,9 +69,9 @@ def compute_weak_subjectivity_period(state: BeaconState) -> uint64: weak_subjectivity_period = MIN_VALIDATOR_WITHDRAWABILITY_DELAY validator_count = len(get_active_validator_indices(state, get_current_epoch(state))) if validator_count >= MIN_PER_EPOCH_CHURN_LIMIT * CHURN_LIMIT_QUOTIENT: - weak_subjectivity_period += SAFETY_DECAY * CHURN_LIMIT_QUOTIENT / (2 * 100) + weak_subjectivity_period += SAFETY_DECAY * CHURN_LIMIT_QUOTIENT // (2 * 100) else: - weak_subjectivity_period += SAFETY_DECAY * validator_count / (2 * 100 * MIN_PER_EPOCH_CHURN_LIMIT) + weak_subjectivity_period += SAFETY_DECAY * validator_count // (2 * 100 * MIN_PER_EPOCH_CHURN_LIMIT) return weak_subjectivity_period ``` From a232efbeb758b131b42c0092225942e13874f1b1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 22 Sep 2020 17:20:55 -0600 Subject: [PATCH 04/48] revert incorrect type fix --- specs/phase0/p2p-interface.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index b337be67e..2b310666d 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -725,7 +725,7 @@ A request MUST NOT have a 0 slot increment, i.e. `step >= 1`. `BeaconBlocksByRange` is primarily used to sync historical blocks. -The request MUST be encoded as a SSZ-container. +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. From 708827413f7db0965567442c23cd47878f07d027 Mon Sep 17 00:00:00 2001 From: protolambda Date: Wed, 23 Sep 2020 22:55:22 +0200 Subject: [PATCH 05/48] cleanup phase1 TOC contents --- specs/phase1/beacon-chain.md | 2 +- specs/phase1/phase1-fork.md | 26 +++++++++----------------- specs/phase1/shard-transition.md | 2 +- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/specs/phase1/beacon-chain.md b/specs/phase1/beacon-chain.md index fd6b63d9c..23ce88aa9 100644 --- a/specs/phase1/beacon-chain.md +++ b/specs/phase1/beacon-chain.md @@ -6,7 +6,7 @@ -**Table of Contents** + - [Introduction](#introduction) - [Custom types](#custom-types) diff --git a/specs/phase1/phase1-fork.md b/specs/phase1/phase1-fork.md index a094b01fc..b4ace1066 100644 --- a/specs/phase1/phase1-fork.md +++ b/specs/phase1/phase1-fork.md @@ -1,28 +1,20 @@ - - -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - -- [Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1](#ethereum-20-phase-1----from-phase-0-to-phase-1) - - [Table of contents](#table-of-contents) - - [Introduction](#introduction) - - [Configuration](#configuration) - - [Fork to Phase 1](#fork-to-phase-1) - - [Fork trigger](#fork-trigger) - - [Upgrading the state](#upgrading-the-state) - - - # Ethereum 2.0 Phase 1 -- From Phase 0 to Phase 1 **Notice**: This document is a work-in-progress for researchers and implementers. ## Table of contents - + + - TODO - +- [Introduction](#introduction) +- [Configuration](#configuration) +- [Fork to Phase 1](#fork-to-phase-1) + - [Fork trigger](#fork-trigger) + - [Upgrading the state](#upgrading-the-state) + + ## Introduction diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index 92e2c5333..df521e451 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -6,7 +6,7 @@ -**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + - [Introduction](#introduction) - [Helper functions](#helper-functions) From 19d6c8336a446aa09677e0b3bc8af04f64556ad2 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 14:37:24 +0800 Subject: [PATCH 06/48] Add IETF BLS draft 04 edge cases test vectors --- tests/core/pyspec/eth2spec/utils/bls.py | 5 +- tests/formats/bls/sign.md | 2 +- tests/generators/bls/main.py | 87 ++++++++++++++++++++----- 3 files changed, 74 insertions(+), 20 deletions(-) diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index 8b91dd64e..dd3172b4b 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -99,4 +99,7 @@ def AggregatePKs(pubkeys): @only_with_bls(alt_return=STUB_SIGNATURE) def SkToPk(SK): - return bls.SkToPk(SK) + if bls == py_ecc_bls: + return bls.SkToPk(SK) + else: + return bls.SkToPk(SK.to_bytes(32, 'big')) diff --git a/tests/formats/bls/sign.md b/tests/formats/bls/sign.md index 1c328755a..93001beee 100644 --- a/tests/formats/bls/sign.md +++ b/tests/formats/bls/sign.md @@ -10,7 +10,7 @@ The test data is declared in a `data.yaml` file: input: privkey: bytes32 -- the private key used for signing message: bytes32 -- input message to sign (a hash) -output: bytes96 -- expected signature +output: BLS Signature -- expected output, single BLS signature or empty. ``` All byte(s) fields are encoded as strings, hexadecimal encoding, prefixed with `0x`. diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 6fec61de0..93121f2a0 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -40,6 +40,7 @@ MESSAGES = [ bytes(b'\x56' * 32), bytes(b'\xab' * 32), ] +SAMPLE_MESSAGE = b'\x12' * 32 PRIVKEYS = [ # Curve order is 256 so private keys are 32 bytes at most. @@ -48,16 +49,30 @@ PRIVKEYS = [ hex_to_int('0x0000000000000000000000000000000047b8192d77bf871b62e87859d653922725724a5c031afeabc60bcef5ff665138'), hex_to_int('0x00000000000000000000000000000000328388aff0d4a5b7dc9205abd374e7e98f3cd9f3418edb4eafda5fb16473d216'), ] +PUBKEYS = [bls.SkToPk(privkey) for privkey in PRIVKEYS] Z1_PUBKEY = b'\xc0' + b'\x00' * 47 NO_SIGNATURE = b'\x00' * 96 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 +ZERO_PRIVKEY = 0 +ZERO_PRIVKEY_BYTES = b'\x00' * 32 + + +def expect_exception(func, *args): + try: + func(*args) + except Exception: + pass + else: + raise Exception("should have raised exception") def case01_sign(): + # Valid cases for privkey in PRIVKEYS: for message in MESSAGES: sig = bls.Sign(privkey, message) + assert sig == milagro_bls.Sign(to_bytes(privkey), message) # double-check with milagro identifier = f'{int_to_hex(privkey)}_{encode_hex(message)}' yield f'sign_case_{(hash(bytes(identifier, "utf-8"))[:8]).hex()}', { 'input': { @@ -66,6 +81,16 @@ def case01_sign(): }, 'output': encode_hex(sig) } + # Edge case: privkey == 0 + expect_exception(bls.Sign, ZERO_PRIVKEY, message) + # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) # TODO: enable it when milagro is ready + yield f'sign_case_zero_privkey', { + 'input': { + 'privkey': ZERO_PRIVKEY_BYTES, + 'message': encode_hex(message), + }, + 'output': None + } def case02_verify(): @@ -120,17 +145,17 @@ def case02_verify(): 'output': False, } - # Valid pubkey and signature with the point at infinity - assert bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE) - assert milagro_bls.Verify(Z1_PUBKEY, message, Z2_SIGNATURE) - yield f'verify_infinity_pubkey_and_infinity_signature', { - 'input': { - 'pubkey': encode_hex(Z1_PUBKEY), - 'message': encode_hex(message), - 'signature': encode_hex(Z2_SIGNATURE), - }, - 'output': True, - } + # Invalid pubkey and signature with the point at infinity + assert not bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) + # assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) # TODO: enable it when milagro is ready + yield f'verify_infinity_pubkey_and_infinity_signature', { + 'input': { + 'pubkey': encode_hex(Z1_PUBKEY), + 'message': encode_hex(SAMPLE_MESSAGE), + 'signature': encode_hex(Z2_SIGNATURE), + }, + 'output': False, + } def case03_aggregate(): @@ -142,13 +167,7 @@ def case03_aggregate(): } # Invalid pubkeys -- len(pubkeys) == 0 - try: - bls.Aggregate([]) - except Exception: - pass - else: - raise Exception("Should have been INVALID") - + expect_exception(bls.Aggregate, []) # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID. # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8 yield f'aggregate_na_signatures', { @@ -231,6 +250,23 @@ def case04_fast_aggregate_verify(): 'output': False, } + # Invalid pubkeys and signature -- pubkeys contains point at infinity + pubkeys = PUBKEYS.copy() + pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] + aggregate_signature = bls.Aggregate(signatures) + assert not bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + # TODO: enable it when milagro is ready + # assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + yield f'fast_aggregate_verify_infinity_pubkey', { + 'input': { + 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], + 'messages': encode_hex(SAMPLE_MESSAGE), + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + def case05_aggregate_verify(): pubkeys = [] @@ -295,6 +331,21 @@ def case05_aggregate_verify(): 'output': False, } + # Invalid pubkeys and signature -- pubkeys contains point at infinity + pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] + messages_with_sample = messages + [SAMPLE_MESSAGE] + assert not bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) + # TODO: enable it when milagro is ready + # assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) + yield f'aggregate_verify_infinity_pubkey', { + 'input': { + 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], + 'messages': [encode_hex(message) for message in messages_with_sample], + 'signature': encode_hex(aggregate_signature), + }, + 'output': False, + } + def create_provider(handler_name: str, test_case_fn: Callable[[], Iterable[Tuple[str, Dict[str, Any]]]]) -> gen_typing.TestProvider: From bdbd2aae375d37da1f51a10fe460df154ea34e64 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 14:40:42 +0800 Subject: [PATCH 07/48] Aggregate G2 point at infinity --- tests/generators/bls/main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 93121f2a0..02ba84040 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -161,9 +161,11 @@ def case02_verify(): def case03_aggregate(): for message in MESSAGES: sigs = [bls.Sign(privkey, message) for privkey in PRIVKEYS] + aggregate_sig = bls.Aggregate(sigs) + assert aggregate_sig == milagro_bls.Aggregate(sigs) yield f'aggregate_{encode_hex(message)}', { 'input': [encode_hex(sig) for sig in sigs], - 'output': encode_hex(bls.Aggregate(sigs)), + 'output': encode_hex(aggregate_sig), } # Invalid pubkeys -- len(pubkeys) == 0 @@ -175,6 +177,14 @@ def case03_aggregate(): 'output': None, } + # Valid to aggregate G2 point at infinity + aggregate_sig = bls.Aggregate([Z2_SIGNATURE]) + assert aggregate_sig == milagro_bls.Aggregate([Z2_SIGNATURE]) == Z2_SIGNATURE + yield f'aggregate_infinity_signature', { + 'input': [Z2_SIGNATURE], + 'output': aggregate_sig, + } + def case04_fast_aggregate_verify(): for i, message in enumerate(MESSAGES): From ad4ad2d8b4ef70f1dcfb459ba0caf365daff0723 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 14:50:01 +0800 Subject: [PATCH 08/48] Bump IETF BLS spec version draft 03 -> draft 04 --- specs/phase0/beacon-chain.md | 2 +- tests/generators/bls/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 69bc48d69..1ceb28d21 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -603,7 +603,7 @@ def bytes_to_uint64(data: bytes) -> uint64: #### BLS Signatures -Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-03](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-03). Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` ciphersuite which implements the following interfaces: +Eth2 makes use of BLS signatures as specified in the [IETF draft BLS specification draft-irtf-cfrg-bls-signature-04](https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04). Specifically, eth2 uses the `BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_` ciphersuite which implements the following interfaces: - `def Sign(SK: int, message: Bytes) -> BLSSignature` - `def Verify(PK: BLSPubkey, message: Bytes, signature: BLSSignature) -> bool` diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 02ba84040..2260e2a8e 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -171,7 +171,7 @@ def case03_aggregate(): # Invalid pubkeys -- len(pubkeys) == 0 expect_exception(bls.Aggregate, []) # No signatures to aggregate. Follow IETF BLS spec, return `None` to represent INVALID. - # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-02#section-2.8 + # https://tools.ietf.org/html/draft-irtf-cfrg-bls-signature-04#section-2.8 yield f'aggregate_na_signatures', { 'input': [], 'output': None, From b43f62de0e0fe50ccba6a82c79fe21b0bee36178 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Fri, 25 Sep 2020 15:05:10 +0800 Subject: [PATCH 09/48] Fix encoding --- tests/generators/bls/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 2260e2a8e..32edc1c41 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -86,7 +86,7 @@ def case01_sign(): # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) # TODO: enable it when milagro is ready yield f'sign_case_zero_privkey', { 'input': { - 'privkey': ZERO_PRIVKEY_BYTES, + 'privkey': encode_hex(ZERO_PRIVKEY_BYTES), 'message': encode_hex(message), }, 'output': None @@ -181,8 +181,8 @@ def case03_aggregate(): aggregate_sig = bls.Aggregate([Z2_SIGNATURE]) assert aggregate_sig == milagro_bls.Aggregate([Z2_SIGNATURE]) == Z2_SIGNATURE yield f'aggregate_infinity_signature', { - 'input': [Z2_SIGNATURE], - 'output': aggregate_sig, + 'input': [encode_hex(Z2_SIGNATURE)], + 'output': encode_hex(aggregate_sig), } From 62d596a91086ae3a9333d4fb39f727960683c236 Mon Sep 17 00:00:00 2001 From: protolambda Date: Fri, 25 Sep 2020 17:59:09 +0200 Subject: [PATCH 10/48] update signature policy link --- specs/phase0/p2p-interface.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index c38fd6bf0..d688f6699 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -246,7 +246,8 @@ Likewise, clients MUST NOT emit or propagate messages larger than this limit. The optional `from` (1), `seqno` (3), `signature` (5) and `key` (6) protobuf fields are omitted from the message, since messages are identified by content, anonymous, and signed where necessary in the application layer. -Starting from Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign` signature policy. +Starting from Gossipsub v1.1, clients MUST enforce this by applying the `StrictNoSign` +[signature policy](https://github.com/libp2p/specs/blob/master/pubsub/README.md#signature-policy-options). The `message-id` of a gossipsub message MUST be the first 8 bytes of the SHA-256 hash of the message data, i.e.: From 157f7e8ef4be3675543980e68581eb4b73284763 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 28 Sep 2020 12:56:08 -0600 Subject: [PATCH 11/48] upgrade config to mainnet values --- configs/mainnet/phase0.yaml | 18 ++++++++---------- configs/minimal/phase0.yaml | 12 ++++++------ specs/phase0/beacon-chain.md | 12 +++++++----- specs/phase0/p2p-interface.md | 2 +- .../epoch_processing/test_process_slashings.py | 10 +++++----- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/configs/mainnet/phase0.yaml b/configs/mainnet/phase0.yaml index fbb12ba2e..569e5ccb5 100644 --- a/configs/mainnet/phase0.yaml +++ b/configs/mainnet/phase0.yaml @@ -1,6 +1,4 @@ # Mainnet preset -# Note: the intention of this file (for now) is to illustrate what a mainnet configuration could look like. -# Some of these constants may still change before the launch of Phase 0. CONFIG_NAME: "mainnet" @@ -28,8 +26,6 @@ HYSTERESIS_QUOTIENT: 4 HYSTERESIS_DOWNWARD_MULTIPLIER: 1 # 5 (plus 1.25) HYSTERESIS_UPWARD_MULTIPLIER: 5 -# 3 -PROPORTIONAL_SLASHING_MULTIPLIER: 3 # Fork Choice @@ -82,8 +78,8 @@ BLS_WITHDRAWAL_PREFIX: 0x00 # Time parameters # --------------------------------------------------------------- -# 172800 seconds (2 days) -GENESIS_DELAY: 172800 +# 604800 seconds (7 days) +GENESIS_DELAY: 604800 # 12 seconds SECONDS_PER_SLOT: 12 # 2**0 (= 1) slots 12 seconds @@ -126,10 +122,12 @@ BASE_REWARD_FACTOR: 64 WHISTLEBLOWER_REWARD_QUOTIENT: 512 # 2**3 (= 8) PROPOSER_REWARD_QUOTIENT: 8 -# 2**24 (= 16,777,216) -INACTIVITY_PENALTY_QUOTIENT: 16777216 -# 2**5 (= 32) -MIN_SLASHING_PENALTY_QUOTIENT: 32 +# 2**26 (= 67,108,864) +INACTIVITY_PENALTY_QUOTIENT: 67108864 +# 2**7 (= 128) (lower safety margin at Phase 0 genesis) +MIN_SLASHING_PENALTY_QUOTIENT: 128 +# 1 (lower safety margin at Phase 0 genesis) +PROPORTIONAL_SLASHING_MULTIPLIER: 1 # Max operations per block diff --git a/configs/minimal/phase0.yaml b/configs/minimal/phase0.yaml index 0ee81738e..aa9f1b126 100644 --- a/configs/minimal/phase0.yaml +++ b/configs/minimal/phase0.yaml @@ -27,8 +27,6 @@ HYSTERESIS_QUOTIENT: 4 HYSTERESIS_DOWNWARD_MULTIPLIER: 1 # 5 (plus 1.25) HYSTERESIS_UPWARD_MULTIPLIER: 5 -# 3 -PROPORTIONAL_SLASHING_MULTIPLIER: 3 # Fork Choice @@ -125,10 +123,12 @@ BASE_REWARD_FACTOR: 64 WHISTLEBLOWER_REWARD_QUOTIENT: 512 # 2**3 (= 8) PROPOSER_REWARD_QUOTIENT: 8 -# 2**24 (= 16,777,216) -INACTIVITY_PENALTY_QUOTIENT: 16777216 -# 2**5 (= 32) -MIN_SLASHING_PENALTY_QUOTIENT: 32 +# [customized] 2**25 (= 33,554,432) +INACTIVITY_PENALTY_QUOTIENT: 33554432 +# [customized] 2**6 (= 64) +MIN_SLASHING_PENALTY_QUOTIENT: 64 +# [customized] 2 (lower safety margin than Phase 0 genesis but different than mainnet config for testing) +PROPORTIONAL_SLASHING_MULTIPLIER: 2 # Max operations per block diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 69bc48d69..caf344c40 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -195,7 +195,6 @@ 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.) @@ -219,7 +218,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | Unit | Duration | | - | - | :-: | :-: | -| `GENESIS_DELAY` | `uint64(172800)` | seconds | 2 days | +| `GENESIS_DELAY` | `uint64(604800)` | seconds | 7 days | | `SECONDS_PER_SLOT` | `uint64(12)` | seconds | 12 seconds | | `SECONDS_PER_ETH1_BLOCK` | `uint64(14)` | seconds | 14 seconds | | `MIN_ATTESTATION_INCLUSION_DELAY` | `uint64(2**0)` (= 1) | slots | 12 seconds | @@ -248,10 +247,13 @@ The following values are (non-configurable) constants used throughout the specif | `BASE_REWARD_FACTOR` | `uint64(2**6)` (= 64) | | `WHISTLEBLOWER_REWARD_QUOTIENT` | `uint64(2**9)` (= 512) | | `PROPOSER_REWARD_QUOTIENT` | `uint64(2**3)` (= 8) | -| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**24)` (= 16,777,216) | -| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**5)` (= 32) | +| `INACTIVITY_PENALTY_QUOTIENT` | `uint64(2**26)` (= 67,108,864) | +| `MIN_SLASHING_PENALTY_QUOTIENT` | `uint64(2**7)` (=128) | +| `PROPORTIONAL_SLASHING_MULTIPLIER` | `uint64(1)` | -- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**12` epochs (about 18 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. +- The `INACTIVITY_PENALTY_QUOTIENT` equals `INVERSE_SQRT_E_DROP_TIME**2` where `INVERSE_SQRT_E_DROP_TIME := 2**13` epochs (about 36 days) is the time it takes the inactivity penalty to reduce the balance of non-participating validators to about `1/sqrt(e) ~= 60.6%`. Indeed, the balance retained by offline validators after `n` epochs is about `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(n**2/2)`; so after `INVERSE_SQRT_E_DROP_TIME` epochs, it is roughly `(1 - 1/INACTIVITY_PENALTY_QUOTIENT)**(INACTIVITY_PENALTY_QUOTIENT/2) ~= 1/sqrt(e)`. Note this value will be upgraded to `2**24` after Phase 0 mainnet stabilizes to provide a faster recovery in the event of an inactivity leak. + +- The `PROPORTIONAL_SLASHING_MULTIPLIER` is set to `1` at initial mainnet launch, resulting in one-third of the minimum accountable safety margin in the event of a finality attack. After Phase 0 mainnet stablizes, this value will be upgraded to `3` to provide the maximal minimum accoutable safety margin. ### Max operations per block diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 76e088e17..b42237731 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1444,7 +1444,7 @@ ENRs contain `fork_digest` which utilizes the `genesis_validators_root` for a cl so prior to knowing genesis, we cannot use `fork_digest` to cleanly find peers on our intended chain. Once genesis data is known, we can then form ENRs and safely find peers. -When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (48hours in mainnet configuration) before `genesis_time`, +When using an eth1 deposit contract for deposits, `fork_digest` will be known `GENESIS_DELAY` (7 days in mainnet configuration) before `genesis_time`, providing ample time to find peers and form initial connections and gossip subnets prior to genesis. ## Compression/Encoding diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index 5101c7548..65f9a134e 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -26,7 +26,7 @@ def slash_validators(spec, state, indices, out_epochs): @with_all_phases @spec_state_test def test_max_penalties(spec, state): - slashed_count = (len(state.validators) // 3) + 1 + slashed_count = (len(state.validators) // spec.PROPORTIONAL_SLASHING_MULTIPLIER) + 1 out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2) slashed_indices = list(range(slashed_count)) @@ -35,7 +35,7 @@ def test_max_penalties(spec, state): total_balance = spec.get_total_active_balance(state) total_penalties = sum(state.slashings) - assert total_balance // 3 <= total_penalties + assert total_balance // spec.PROPORTIONAL_SLASHING_MULTIPLIER <= total_penalties yield from run_process_slashings(spec, state) @@ -91,12 +91,12 @@ def test_scaled_penalties(spec, state): state.slashings[5] = base + (incr * 6) state.slashings[spec.EPOCHS_PER_SLASHINGS_VECTOR - 1] = base + (incr * 7) - slashed_count = len(state.validators) // 4 + slashed_count = len(state.validators) // (spec.PROPORTIONAL_SLASHING_MULTIPLIER + 1) assert slashed_count > 10 # make the balances non-uniform. - # Otherwise it would just be a simple 3/4 balance slashing. Test the per-validator scaled penalties. + # Otherwise it would just be a simple balance slashing. Test the per-validator scaled penalties. diff = spec.MAX_EFFECTIVE_BALANCE - base increments = diff // incr for i in range(10): @@ -129,7 +129,7 @@ def test_scaled_penalties(spec, state): v = state.validators[i] expected_penalty = ( v.effective_balance // spec.EFFECTIVE_BALANCE_INCREMENT - * (3 * total_penalties) + * (spec.PROPORTIONAL_SLASHING_MULTIPLIER * total_penalties) // (total_balance) * spec.EFFECTIVE_BALANCE_INCREMENT ) From 4e2c7d20b7b87aa3c11eecc920e8b17c04c36dc6 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 28 Sep 2020 17:55:54 -0600 Subject: [PATCH 12/48] add additional genesis initialization tests --- .../pyspec/eth2spec/test/helpers/deposits.py | 45 +++++- .../phase0/genesis/test_initialization.py | 132 ++++++++++++++++-- .../test/phase0/genesis/test_validity.py | 8 +- 3 files changed, 171 insertions(+), 14 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index 6a2e30497..418ab1579 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -1,3 +1,5 @@ +import random + from eth2spec.test.helpers.keys import pubkeys, privkeys from eth2spec.utils import bls from eth2spec.utils.merkle_minimal import calc_merkle_tree_from_leaves, get_merkle_proof @@ -62,11 +64,16 @@ def deposit_from_context(spec, deposit_data_list, index): return deposit, root, deposit_data_list -def prepare_genesis_deposits(spec, genesis_validator_count, amount, signed=False, deposit_data_list=None): +def prepare_full_genesis_deposits(spec, + amount, + pubkey_max_range, + pubkey_min_range=0, + signed=False, + deposit_data_list=None): if deposit_data_list is None: deposit_data_list = [] genesis_deposits = [] - for validator_index in range(genesis_validator_count): + for validator_index in range(pubkey_min_range, pubkey_max_range): pubkey = pubkeys[validator_index] privkey = privkeys[validator_index] # insecurely use pubkey as withdrawal key if no credentials provided @@ -85,6 +92,40 @@ def prepare_genesis_deposits(spec, genesis_validator_count, amount, signed=False return genesis_deposits, root, deposit_data_list +def prepare_random_genesis_deposits(spec, + num_deposits, + max_pubkey_index, + min_pubkey_index=0, + max_amount=None, + min_amount=None, + deposit_data_list=None): + if max_amount is None: + max_amount = spec.MAX_EFFECTIVE_BALANCE + if min_amount is None: + min_amount = spec.MIN_DEPOSIT_AMOUNT + if deposit_data_list is None: + deposit_data_list = [] + deposits = [] + for _ in range(num_deposits): + pubkey_index = random.randint(min_pubkey_index, max_pubkey_index) + pubkey = pubkeys[pubkey_index] + privkey = privkeys[pubkey_index] + amount = random.randint(min_amount, max_amount) + random_byte = bytes([random.randint(0, 255)]) + withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(random_byte)[1:] + deposit, root, deposit_data_list = build_deposit( + spec, + deposit_data_list, + pubkey, + privkey, + amount, + withdrawal_credentials, + signed=True, + ) + deposits.append(deposit) + return deposits, root, deposit_data_list + + def prepare_state_and_deposit(spec, state, validator_index, amount, withdrawal_credentials=None, signed=False): """ Prepare the state for the deposit, and create a deposit for the given validator, depositing the given amount. diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py index faade2d17..8cd66ae2d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py @@ -1,6 +1,7 @@ from eth2spec.test.context import PHASE0, spec_test, with_phases, single_phase from eth2spec.test.helpers.deposits import ( - prepare_genesis_deposits, + prepare_full_genesis_deposits, + prepare_random_genesis_deposits, ) @@ -9,7 +10,12 @@ from eth2spec.test.helpers.deposits import ( @single_phase def test_initialize_beacon_state_from_eth1(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - deposits, deposit_root, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) + deposits, deposit_root, _ = prepare_full_genesis_deposits( + spec, + spec.MAX_EFFECTIVE_BALANCE, + deposit_count, + signed=True, + ) eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME @@ -37,14 +43,18 @@ def test_initialize_beacon_state_from_eth1(spec): @single_phase def test_initialize_beacon_state_some_small_balances(spec): main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - main_deposits, _, deposit_data_list = prepare_genesis_deposits(spec, main_deposit_count, - spec.MAX_EFFECTIVE_BALANCE, signed=True) + main_deposits, _, deposit_data_list = prepare_full_genesis_deposits( + spec, spec.MAX_EFFECTIVE_BALANCE, + pubkey_max_range=main_deposit_count, signed=True, + ) # For deposits above, and for another deposit_count, add a balance of EFFECTIVE_BALANCE_INCREMENT small_deposit_count = main_deposit_count * 2 - small_deposits, deposit_root, _ = prepare_genesis_deposits(spec, small_deposit_count, - spec.MIN_DEPOSIT_AMOUNT, - signed=True, - deposit_data_list=deposit_data_list) + small_deposits, deposit_root, _ = prepare_full_genesis_deposits( + spec, spec.MIN_DEPOSIT_AMOUNT, + pubkey_max_range=small_deposit_count, + signed=True, + deposit_data_list=deposit_data_list, + ) deposits = main_deposits + small_deposits eth1_block_hash = b'\x12' * 32 @@ -67,3 +77,109 @@ def test_initialize_beacon_state_some_small_balances(spec): # yield state yield 'state', state + + +@with_phases([PHASE0]) +@spec_test +@single_phase +def test_initialize_beacon_state_one_topup_activation(spec): + # submit all but one deposit as MAX_EFFECTIVE_BALANCE + main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 + main_deposits, _, deposit_data_list = prepare_full_genesis_deposits( + spec, spec.MAX_EFFECTIVE_BALANCE, + pubkey_max_range=main_deposit_count, signed=True, + ) + + # submit last pubkey deposit as MAX_EFFECTIVE_BALANCE - MIN_DEPOSIT_AMOUNT + partial_deposits, _, deposit_data_list = prepare_full_genesis_deposits( + spec, spec.MAX_EFFECTIVE_BALANCE - spec.MIN_DEPOSIT_AMOUNT, + pubkey_max_range=main_deposit_count + 1, + pubkey_min_range=main_deposit_count, + signed=True, + deposit_data_list=deposit_data_list, + ) + + # submit last pubkey deposit as MIN_DEPOSIT_AMOUNT to complete the deposit + completed_deposits, deposit_root, deposit_data_list = prepare_full_genesis_deposits( + spec, spec.MIN_DEPOSIT_AMOUNT, + pubkey_max_range=main_deposit_count + 1, + pubkey_min_range=main_deposit_count, + signed=True, + deposit_data_list=deposit_data_list, + ) + + deposits = main_deposits + partial_deposits + completed_deposits + + eth1_block_hash = b'\x13' * 32 + eth1_timestamp = spec.MIN_GENESIS_TIME + + yield 'eth1_block_hash', eth1_block_hash + yield 'eth1_timestamp', eth1_timestamp + yield 'deposits', deposits + + # initialize beacon_state + state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) + assert spec.is_valid_genesis_state(state) + + # yield state + yield 'state', state + + +@with_phases([PHASE0]) +@spec_test +@single_phase +def test_initialize_beacon_state_random_invalid_genesis(spec): + # Make a bunch of random deposits + deposits, _, deposit_data_list = prepare_random_genesis_deposits( + spec, + num_deposits=20, + max_pubkey_index=10, + ) + eth1_block_hash = b'\x14' * 32 + eth1_timestamp = spec.MIN_GENESIS_TIME + 1 + + yield 'eth1_block_hash', eth1_block_hash + yield 'eth1_timestamp', eth1_timestamp + yield 'deposits', deposits + + # initialize beacon_state + state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) + assert not spec.is_valid_genesis_state(state) + + yield 'state', state + + +@with_phases([PHASE0]) +@spec_test +@single_phase +def test_initialize_beacon_state_random_valid_genesis(spec): + # Make a bunch of random deposits + random_deposits, _, deposit_data_list = prepare_random_genesis_deposits( + spec, + num_deposits=30, + min_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 5, + max_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 5, + ) + + # Then make spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT full deposits + full_deposits, _, _ = prepare_full_genesis_deposits( + spec, + spec.MAX_EFFECTIVE_BALANCE, + pubkey_max_range=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, + signed=True, + deposit_data_list=deposit_data_list + ) + + deposits = random_deposits + full_deposits + eth1_block_hash = b'\x15' * 32 + eth1_timestamp = spec.MIN_GENESIS_TIME + 2 + + yield 'eth1_block_hash', eth1_block_hash + yield 'eth1_timestamp', eth1_timestamp + yield 'deposits', deposits + + # initialize beacon_state + state = spec.initialize_beacon_state_from_eth1(eth1_block_hash, eth1_timestamp, deposits) + assert spec.is_valid_genesis_state(state) + + yield 'state', state diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py index dbaf3f951..832ad597f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py @@ -1,12 +1,12 @@ from eth2spec.test.context import PHASE0, spec_test, with_phases, single_phase from eth2spec.test.helpers.deposits import ( - prepare_genesis_deposits, + prepare_full_genesis_deposits, ) def create_valid_beacon_state(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) + deposits, _, _ = prepare_full_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME @@ -69,7 +69,7 @@ def test_is_valid_genesis_state_true_more_balance(spec): @single_phase def test_is_valid_genesis_state_true_one_more_validator(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1 - deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) + deposits, _, _ = prepare_full_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME @@ -83,7 +83,7 @@ def test_is_valid_genesis_state_true_one_more_validator(spec): @single_phase def test_is_valid_genesis_state_false_not_enough_validator(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 - deposits, _, _ = prepare_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) + deposits, _, _ = prepare_full_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME From 76d69263fc58c5dc3dd252f4f7c0266984712ea4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 28 Sep 2020 18:01:13 -0600 Subject: [PATCH 13/48] use better rng practice for reproducibility --- tests/core/pyspec/eth2spec/test/helpers/deposits.py | 11 ++++++----- .../test/phase0/genesis/test_initialization.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index 418ab1579..150de07db 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -1,4 +1,4 @@ -import random +from random import Random from eth2spec.test.helpers.keys import pubkeys, privkeys from eth2spec.utils import bls @@ -98,7 +98,8 @@ def prepare_random_genesis_deposits(spec, min_pubkey_index=0, max_amount=None, min_amount=None, - deposit_data_list=None): + deposit_data_list=None, + rng=Random(3131)): if max_amount is None: max_amount = spec.MAX_EFFECTIVE_BALANCE if min_amount is None: @@ -107,11 +108,11 @@ def prepare_random_genesis_deposits(spec, deposit_data_list = [] deposits = [] for _ in range(num_deposits): - pubkey_index = random.randint(min_pubkey_index, max_pubkey_index) + pubkey_index = rng.randint(min_pubkey_index, max_pubkey_index) pubkey = pubkeys[pubkey_index] privkey = privkeys[pubkey_index] - amount = random.randint(min_amount, max_amount) - random_byte = bytes([random.randint(0, 255)]) + amount = rng.randint(min_amount, max_amount) + random_byte = bytes([rng.randint(0, 255)]) withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(random_byte)[1:] deposit, root, deposit_data_list = build_deposit( spec, diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py index 8cd66ae2d..73ee5528f 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py @@ -156,7 +156,7 @@ def test_initialize_beacon_state_random_valid_genesis(spec): # Make a bunch of random deposits random_deposits, _, deposit_data_list = prepare_random_genesis_deposits( spec, - num_deposits=30, + num_deposits=20, min_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 5, max_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 5, ) From 0e2e494d7e71ff01e2fc55f00c9e632c9c9f1db8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Mon, 28 Sep 2020 19:32:42 -0600 Subject: [PATCH 14/48] fix function signature calls on deposit helpers --- .../pyspec/eth2spec/test/helpers/deposits.py | 22 +++++++++---------- .../test/phase0/genesis/test_validity.py | 21 +++++++++++++++--- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index 150de07db..565f4bb53 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -80,12 +80,12 @@ def prepare_full_genesis_deposits(spec, withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] deposit, root, deposit_data_list = build_deposit( spec, - deposit_data_list, - pubkey, - privkey, - amount, - withdrawal_credentials, - signed, + deposit_data_list=deposit_data_list, + pubkey=pubkey, + privkey=privkey, + amount=amount, + withdrawal_credentials=withdrawal_credentials, + signed=signed, ) genesis_deposits.append(deposit) @@ -116,11 +116,11 @@ def prepare_random_genesis_deposits(spec, withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(random_byte)[1:] deposit, root, deposit_data_list = build_deposit( spec, - deposit_data_list, - pubkey, - privkey, - amount, - withdrawal_credentials, + deposit_data_list=deposit_data_list, + pubkey=pubkey, + privkey=privkey, + amount=amount, + withdrawal_credentials=withdrawal_credentials, signed=True, ) deposits.append(deposit) diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py index 832ad597f..486f65076 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py @@ -6,7 +6,12 @@ from eth2spec.test.helpers.deposits import ( def create_valid_beacon_state(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - deposits, _, _ = prepare_full_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) + deposits, _, _ = prepare_full_genesis_deposits( + spec, + amount=spec.MAX_EFFECTIVE_BALANCE, + pubkey_max_range=deposit_count, + signed=True, + ) eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME @@ -69,7 +74,12 @@ def test_is_valid_genesis_state_true_more_balance(spec): @single_phase def test_is_valid_genesis_state_true_one_more_validator(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 1 - deposits, _, _ = prepare_full_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) + deposits, _, _ = prepare_full_genesis_deposits( + spec, + amount=spec.MAX_EFFECTIVE_BALANCE, + pubkey_max_range=deposit_count, + signed=True, + ) eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME @@ -83,7 +93,12 @@ def test_is_valid_genesis_state_true_one_more_validator(spec): @single_phase def test_is_valid_genesis_state_false_not_enough_validator(spec): deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 - deposits, _, _ = prepare_full_genesis_deposits(spec, deposit_count, spec.MAX_EFFECTIVE_BALANCE, signed=True) + deposits, _, _ = prepare_full_genesis_deposits( + spec, + amount=spec.MAX_EFFECTIVE_BALANCE, + pubkey_max_range=deposit_count, + signed=True, + ) eth1_block_hash = b'\x12' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME From 9f36fd695453e106fe81f7b4ffb848317a0e6a45 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 29 Sep 2020 09:54:44 -0600 Subject: [PATCH 15/48] PR feedback. thanks @hwwhww --- .../pyspec/eth2spec/test/helpers/deposits.py | 14 ++++----- .../phase0/genesis/test_initialization.py | 30 +++++++++---------- .../test/phase0/genesis/test_validity.py | 6 ++-- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index 565f4bb53..9f7d96981 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -66,16 +66,16 @@ def deposit_from_context(spec, deposit_data_list, index): def prepare_full_genesis_deposits(spec, amount, - pubkey_max_range, - pubkey_min_range=0, + deposit_count, + min_pubkey_index=0, signed=False, deposit_data_list=None): if deposit_data_list is None: deposit_data_list = [] genesis_deposits = [] - for validator_index in range(pubkey_min_range, pubkey_max_range): - pubkey = pubkeys[validator_index] - privkey = privkeys[validator_index] + for pubkey_index in range(min_pubkey_index, min_pubkey_index + deposit_count): + pubkey = pubkeys[pubkey_index] + privkey = privkeys[pubkey_index] # insecurely use pubkey as withdrawal key if no credentials provided withdrawal_credentials = spec.BLS_WITHDRAWAL_PREFIX + spec.hash(pubkey)[1:] deposit, root, deposit_data_list = build_deposit( @@ -93,7 +93,7 @@ def prepare_full_genesis_deposits(spec, def prepare_random_genesis_deposits(spec, - num_deposits, + deposit_count, max_pubkey_index, min_pubkey_index=0, max_amount=None, @@ -107,7 +107,7 @@ def prepare_random_genesis_deposits(spec, if deposit_data_list is None: deposit_data_list = [] deposits = [] - for _ in range(num_deposits): + for _ in range(deposit_count): pubkey_index = rng.randint(min_pubkey_index, max_pubkey_index) pubkey = pubkeys[pubkey_index] privkey = privkeys[pubkey_index] diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py index 73ee5528f..b39f5ecfd 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_initialization.py @@ -45,13 +45,13 @@ def test_initialize_beacon_state_some_small_balances(spec): main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT main_deposits, _, deposit_data_list = prepare_full_genesis_deposits( spec, spec.MAX_EFFECTIVE_BALANCE, - pubkey_max_range=main_deposit_count, signed=True, + deposit_count=main_deposit_count, signed=True, ) # For deposits above, and for another deposit_count, add a balance of EFFECTIVE_BALANCE_INCREMENT small_deposit_count = main_deposit_count * 2 small_deposits, deposit_root, _ = prepare_full_genesis_deposits( spec, spec.MIN_DEPOSIT_AMOUNT, - pubkey_max_range=small_deposit_count, + deposit_count=small_deposit_count, signed=True, deposit_data_list=deposit_data_list, ) @@ -83,32 +83,32 @@ def test_initialize_beacon_state_some_small_balances(spec): @spec_test @single_phase def test_initialize_beacon_state_one_topup_activation(spec): - # submit all but one deposit as MAX_EFFECTIVE_BALANCE + # Submit all but one deposit as MAX_EFFECTIVE_BALANCE main_deposit_count = spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 1 main_deposits, _, deposit_data_list = prepare_full_genesis_deposits( spec, spec.MAX_EFFECTIVE_BALANCE, - pubkey_max_range=main_deposit_count, signed=True, + deposit_count=main_deposit_count, signed=True, ) - # submit last pubkey deposit as MAX_EFFECTIVE_BALANCE - MIN_DEPOSIT_AMOUNT + # Submit last pubkey deposit as MAX_EFFECTIVE_BALANCE - MIN_DEPOSIT_AMOUNT partial_deposits, _, deposit_data_list = prepare_full_genesis_deposits( spec, spec.MAX_EFFECTIVE_BALANCE - spec.MIN_DEPOSIT_AMOUNT, - pubkey_max_range=main_deposit_count + 1, - pubkey_min_range=main_deposit_count, + deposit_count=1, + min_pubkey_index=main_deposit_count, signed=True, deposit_data_list=deposit_data_list, ) - # submit last pubkey deposit as MIN_DEPOSIT_AMOUNT to complete the deposit - completed_deposits, deposit_root, deposit_data_list = prepare_full_genesis_deposits( + # Top up thelast pubkey deposit as MIN_DEPOSIT_AMOUNT to complete the deposit + top_up_deposits, _, _ = prepare_full_genesis_deposits( spec, spec.MIN_DEPOSIT_AMOUNT, - pubkey_max_range=main_deposit_count + 1, - pubkey_min_range=main_deposit_count, + deposit_count=1, + min_pubkey_index=main_deposit_count, signed=True, deposit_data_list=deposit_data_list, ) - deposits = main_deposits + partial_deposits + completed_deposits + deposits = main_deposits + partial_deposits + top_up_deposits eth1_block_hash = b'\x13' * 32 eth1_timestamp = spec.MIN_GENESIS_TIME @@ -132,7 +132,7 @@ def test_initialize_beacon_state_random_invalid_genesis(spec): # Make a bunch of random deposits deposits, _, deposit_data_list = prepare_random_genesis_deposits( spec, - num_deposits=20, + deposit_count=20, max_pubkey_index=10, ) eth1_block_hash = b'\x14' * 32 @@ -156,7 +156,7 @@ def test_initialize_beacon_state_random_valid_genesis(spec): # Make a bunch of random deposits random_deposits, _, deposit_data_list = prepare_random_genesis_deposits( spec, - num_deposits=20, + deposit_count=20, min_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT - 5, max_pubkey_index=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT + 5, ) @@ -165,7 +165,7 @@ def test_initialize_beacon_state_random_valid_genesis(spec): full_deposits, _, _ = prepare_full_genesis_deposits( spec, spec.MAX_EFFECTIVE_BALANCE, - pubkey_max_range=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, + deposit_count=spec.MIN_GENESIS_ACTIVE_VALIDATOR_COUNT, signed=True, deposit_data_list=deposit_data_list ) diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py index 486f65076..7bdfe1a42 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py @@ -9,7 +9,7 @@ def create_valid_beacon_state(spec): deposits, _, _ = prepare_full_genesis_deposits( spec, amount=spec.MAX_EFFECTIVE_BALANCE, - pubkey_max_range=deposit_count, + deposit_count=deposit_count, signed=True, ) @@ -77,7 +77,7 @@ def test_is_valid_genesis_state_true_one_more_validator(spec): deposits, _, _ = prepare_full_genesis_deposits( spec, amount=spec.MAX_EFFECTIVE_BALANCE, - pubkey_max_range=deposit_count, + deposit_count=deposit_count, signed=True, ) @@ -96,7 +96,7 @@ def test_is_valid_genesis_state_false_not_enough_validator(spec): deposits, _, _ = prepare_full_genesis_deposits( spec, amount=spec.MAX_EFFECTIVE_BALANCE, - pubkey_max_range=deposit_count, + deposit_count=deposit_count, signed=True, ) From 4613c6b3338708d60526943ec971799d1994d41e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 1 Oct 2020 00:44:47 +0800 Subject: [PATCH 16/48] Bump py_ecc to 5.0.0 --- setup.py | 2 +- tests/generators/bls/requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index b024e8d7f..350f4aedd 100644 --- a/setup.py +++ b/setup.py @@ -536,7 +536,7 @@ setup( "eth-utils>=1.3.0,<2", "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.9.4", - "py_ecc==4.0.0", + "py_ecc==5.0.0", "milagro_bls_binding==1.3.0", "dataclasses==0.6", "remerkleable==0.1.17", diff --git a/tests/generators/bls/requirements.txt b/tests/generators/bls/requirements.txt index 254705282..fd54b5b32 100644 --- a/tests/generators/bls/requirements.txt +++ b/tests/generators/bls/requirements.txt @@ -1,4 +1,4 @@ -py_ecc==4.0.0 +py_ecc==5.0.0 eth-utils==1.6.0 ../../core/gen_helpers ../../../ From 4d3ac72473b2ada262aeb415cbb595287649716e Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Mon, 5 Oct 2020 21:55:49 +0800 Subject: [PATCH 17/48] Bump milagro_bls_binding to `1.4.0`, handle the exception cases --- setup.py | 2 +- tests/core/pyspec/eth2spec/utils/bls.py | 9 +++++++++ tests/generators/bls/main.py | 10 +++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 350f4aedd..451da1b73 100644 --- a/setup.py +++ b/setup.py @@ -537,7 +537,7 @@ setup( "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.9.4", "py_ecc==5.0.0", - "milagro_bls_binding==1.3.0", + "milagro_bls_binding==1.4.0", "dataclasses==0.6", "remerkleable==0.1.17", "ruamel.yaml==0.16.5", diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index dd3172b4b..88edbac61 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -10,6 +10,7 @@ bls = py_ecc_bls STUB_SIGNATURE = b'\x11' * 96 STUB_PUBKEY = b'\x22' * 48 +Z1_PUBKEY = b'\xc0' + b'\x00' * 47 Z2_SIGNATURE = b'\xc0' + b'\x00' * 95 STUB_COORDINATES = _signature_to_G2(Z2_SIGNATURE) @@ -66,6 +67,11 @@ def AggregateVerify(pubkeys, messages, signature): @only_with_bls(alt_return=True) def FastAggregateVerify(pubkeys, message, signature): + # TODO: remove it when milagro_bls_binding is fixed + # https://github.com/ChihChengLiang/milagro_bls_binding/issues/19 + if Z1_PUBKEY in pubkeys: + return False + try: result = bls.FastAggregateVerify(list(pubkeys), message, signature) except Exception: @@ -81,6 +87,9 @@ def Aggregate(signatures): @only_with_bls(alt_return=STUB_SIGNATURE) def Sign(SK, message): + # TODO: remove it when https://github.com/sigp/milagro_bls/issues/39 is fixed + if SK == 0: + raise Exception("SK should not be zero") if bls == py_ecc_bls: return bls.Sign(SK, message) else: diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 32edc1c41..8cdc2a94b 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -83,7 +83,8 @@ def case01_sign(): } # Edge case: privkey == 0 expect_exception(bls.Sign, ZERO_PRIVKEY, message) - # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) # TODO: enable it when milagro is ready + # TODO enable it when milagro_bls is ready for IETF BLS draft 04 + # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) yield f'sign_case_zero_privkey', { 'input': { 'privkey': encode_hex(ZERO_PRIVKEY_BYTES), @@ -147,7 +148,7 @@ def case02_verify(): # Invalid pubkey and signature with the point at infinity assert not bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) - # assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) # TODO: enable it when milagro is ready + assert not milagro_bls.Verify(Z1_PUBKEY, SAMPLE_MESSAGE, Z2_SIGNATURE) yield f'verify_infinity_pubkey_and_infinity_signature', { 'input': { 'pubkey': encode_hex(Z1_PUBKEY), @@ -266,7 +267,7 @@ def case04_fast_aggregate_verify(): signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] aggregate_signature = bls.Aggregate(signatures) assert not bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) - # TODO: enable it when milagro is ready + # TODO enable it when milagro_bls is ready for IETF BLS draft 04 # assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) yield f'fast_aggregate_verify_infinity_pubkey', { 'input': { @@ -345,8 +346,7 @@ def case05_aggregate_verify(): pubkeys_with_infinity = pubkeys + [Z1_PUBKEY] messages_with_sample = messages + [SAMPLE_MESSAGE] assert not bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) - # TODO: enable it when milagro is ready - # assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) + assert not milagro_bls.AggregateVerify(pubkeys_with_infinity, messages_with_sample, aggregate_signature) yield f'aggregate_verify_infinity_pubkey', { 'input': { 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], From 00a19e583eb5de58ada94861862cb32a09a383c7 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 6 Oct 2020 16:17:29 +0800 Subject: [PATCH 18/48] Bump milagro_bls_binding to 1.5.0 --- setup.py | 2 +- tests/core/pyspec/eth2spec/utils/bls.py | 8 -------- tests/generators/bls/main.py | 6 ++---- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/setup.py b/setup.py index 451da1b73..9c2de0751 100644 --- a/setup.py +++ b/setup.py @@ -537,7 +537,7 @@ setup( "eth-typing>=2.1.0,<3.0.0", "pycryptodome==3.9.4", "py_ecc==5.0.0", - "milagro_bls_binding==1.4.0", + "milagro_bls_binding==1.5.0", "dataclasses==0.6", "remerkleable==0.1.17", "ruamel.yaml==0.16.5", diff --git a/tests/core/pyspec/eth2spec/utils/bls.py b/tests/core/pyspec/eth2spec/utils/bls.py index 88edbac61..dc4daca49 100644 --- a/tests/core/pyspec/eth2spec/utils/bls.py +++ b/tests/core/pyspec/eth2spec/utils/bls.py @@ -67,11 +67,6 @@ def AggregateVerify(pubkeys, messages, signature): @only_with_bls(alt_return=True) def FastAggregateVerify(pubkeys, message, signature): - # TODO: remove it when milagro_bls_binding is fixed - # https://github.com/ChihChengLiang/milagro_bls_binding/issues/19 - if Z1_PUBKEY in pubkeys: - return False - try: result = bls.FastAggregateVerify(list(pubkeys), message, signature) except Exception: @@ -87,9 +82,6 @@ def Aggregate(signatures): @only_with_bls(alt_return=STUB_SIGNATURE) def Sign(SK, message): - # TODO: remove it when https://github.com/sigp/milagro_bls/issues/39 is fixed - if SK == 0: - raise Exception("SK should not be zero") if bls == py_ecc_bls: return bls.Sign(SK, message) else: diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 8cdc2a94b..7a74404d6 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -83,8 +83,7 @@ def case01_sign(): } # Edge case: privkey == 0 expect_exception(bls.Sign, ZERO_PRIVKEY, message) - # TODO enable it when milagro_bls is ready for IETF BLS draft 04 - # expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) + expect_exception(milagro_bls.Sign, ZERO_PRIVKEY_BYTES, message) yield f'sign_case_zero_privkey', { 'input': { 'privkey': encode_hex(ZERO_PRIVKEY_BYTES), @@ -267,8 +266,7 @@ def case04_fast_aggregate_verify(): signatures = [bls.Sign(privkey, SAMPLE_MESSAGE) for privkey in PRIVKEYS] aggregate_signature = bls.Aggregate(signatures) assert not bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) - # TODO enable it when milagro_bls is ready for IETF BLS draft 04 - # assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) + assert not milagro_bls.FastAggregateVerify(pubkeys_with_infinity, SAMPLE_MESSAGE, aggregate_signature) yield f'fast_aggregate_verify_infinity_pubkey', { 'input': { 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], From 2e09c91e39accdaa2be187e5f51aa2b005a6180c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 6 Oct 2020 11:34:33 -0600 Subject: [PATCH 19/48] fix test generators for mainnet max slashing penalties --- .../test/phase0/epoch_processing/test_process_slashings.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py index 65f9a134e..3e96f8b58 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_slashings.py @@ -26,7 +26,12 @@ def slash_validators(spec, state, indices, out_epochs): @with_all_phases @spec_state_test def test_max_penalties(spec, state): - slashed_count = (len(state.validators) // spec.PROPORTIONAL_SLASHING_MULTIPLIER) + 1 + # Slashed count to ensure that enough validators are slashed to induce maximum penalties + slashed_count = min( + (len(state.validators) // spec.PROPORTIONAL_SLASHING_MULTIPLIER) + 1, + # Can't slash more than validator count! + len(state.validators) + ) out_epoch = spec.get_current_epoch(state) + (spec.EPOCHS_PER_SLASHINGS_VECTOR // 2) slashed_indices = list(range(slashed_count)) From a365fcb03b28727f1427b53f64e75db87f6dd0fe Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 6 Oct 2020 14:42:23 -0600 Subject: [PATCH 20/48] fix message-id issues --- specs/phase0/p2p-interface.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index b42237731..c50ab362a 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -243,11 +243,21 @@ Each gossipsub [message](https://github.com/libp2p/go-libp2p-pubsub/blob/master/ Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. -The `message-id` of a gossipsub message MUST be the first 8 bytes of the SHA-256 hash of the message data, i.e.: +The `message-id` of a gossipsub message MUST be the following 20 byte value computed from the message data: +* If `message.data` can be snappy decompressed: + * Let `message-id` be the first 20 bytes of the `SHA256` hash of the snappy decompressed message data, + i.e. `SHA256(snappy_decompress(message.data))[:20]`. + * Then set the most significant bit of the left-most byte of `message-id` to 1, + i.e. `message-id[0] |= (0x01 << 7)`. +* Otherwise: + * Let `message-id` be the first 20 bytes of the `SHA256` hash of the raw message data, + i.e. `SHA256(message.data)[:20]`. + * Then set the most significant bit of the left-most byte of `message-id` to 0, + i.e. `message-id[0] &= (0xFF >> 1)`. -```python - message-id: SHA256(message.data)[0:8] -``` +*Note*: The above logic handles two exceptional cases: +(1) multiple snappy `data` can decompress to the same value, +and (2) some message `data` can fail to snappy decompress altogether. The payload is carried in the `data` field of a gossipsub message, and varies depending on the topic: From 6287875baf53e7a062cb6246e728ff7d1fd3d84a Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Oct 2020 11:57:25 -0600 Subject: [PATCH 21/48] use domain byte to isolate message-id domains --- specs/phase0/p2p-interface.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index c50ab362a..c9416f328 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -178,6 +178,9 @@ This section outlines constants that are used in this spec. | `RESP_TIMEOUT` | `10s` | The maximum time for complete response transfer. | | `ATTESTATION_PROPAGATION_SLOT_RANGE` | `32` | The maximum number of slots during which an attestation can be propagated. | | `MAXIMUM_GOSSIP_CLOCK_DISPARITY` | `500ms` | The maximum milliseconds of clock disparity assumed between honest nodes. | +| `MESSAGE_DOMAIN_INVALID_SNAPPY` | `0x00000000` | 4-byte domain for gossip message-id isolation of *invalid* snappy messages | +| `MESSAGE_DOMAIN_VALID_SNAPPY` | `0x01000000` | 4-byte domain for gossip message-id isolation of *valid* snappy messages | + ## MetaData @@ -244,16 +247,12 @@ Clients MUST reject (fail validation) messages that are over this size limit. Likewise, clients MUST NOT emit or propagate messages larger than this limit. The `message-id` of a gossipsub message MUST be the following 20 byte value computed from the message data: -* If `message.data` can be snappy decompressed: - * Let `message-id` be the first 20 bytes of the `SHA256` hash of the snappy decompressed message data, - i.e. `SHA256(snappy_decompress(message.data))[:20]`. - * Then set the most significant bit of the left-most byte of `message-id` to 1, - i.e. `message-id[0] |= (0x01 << 7)`. -* Otherwise: - * Let `message-id` be the first 20 bytes of the `SHA256` hash of the raw message data, - i.e. `SHA256(message.data)[:20]`. - * Then set the most significant bit of the left-most byte of `message-id` to 0, - i.e. `message-id[0] &= (0xFF >> 1)`. +* If `message.data` has a valid snappy decompression, set `message-id` to the first 20 bytes of the `SHA256` hash of + the concatenation of `MESSAGE_DOMAIN_VALID_SNAPPY` with the snappy decompressed message data, + i.e. `SHA256(MESSAGE_DOMAIN_VALID_SNAPPY + snappy_decompress(message.data))[:20]`. +* Otherwise, set `message-id` to the first 20 bytes of the `SHA256` hash of + the concatenation of `MESSAGE_DOMAIN_INVALID_SNAPPY` with the raw message data, + i.e. `SHA256(MESSAGE_DOMAIN_INVALID_SNAPPY + message.data)[:20]`. *Note*: The above logic handles two exceptional cases: (1) multiple snappy `data` can decompress to the same value, From 8cda237884a4db452b73c8e66100b2914eb79567 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Oct 2020 12:12:02 -0600 Subject: [PATCH 22/48] note v5.1 in discv5 p2p spec --- specs/phase0/p2p-interface.md | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index b42237731..cb84bef6c 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -54,8 +54,6 @@ It consists of four main sections: - [ENR structure](#enr-structure) - [Attestation subnet bitfield](#attestation-subnet-bitfield) - [`eth2` field](#eth2-field) - - [General capabilities](#general-capabilities) - - [Topic advertisement](#topic-advertisement) - [Design decision rationale](#design-decision-rationale) - [Transport](#transport-1) - [Why are we defining specific transports?](#why-are-we-defining-specific-transports) @@ -846,13 +844,11 @@ The response MUST consist of a single `response_chunk`. ## The discovery domain: discv5 -Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) is used for peer discovery. +Discovery Version 5 ([discv5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md)) (Protocol version v5.1) is used for peer discovery. `discv5` is a standalone protocol, running on UDP on a dedicated port, meant for peer discovery only. `discv5` supports self-certified, flexible peer records (ENRs) and topic-based advertisement, both of which are (or will be) requirements in this context. -:warning: Under construction. :warning: - ### Integration into libp2p stacks `discv5` SHOULD be integrated into the client’s libp2p stack by implementing an adaptor @@ -935,19 +931,6 @@ Clients MAY connect to peers with the same `fork_digest` but a different `next_f Unless `ENRForkID` is manually updated to matching prior to the earlier `next_fork_epoch` of the two clients, these connecting clients will be unable to successfully interact starting at the earlier `next_fork_epoch`. -#### General capabilities - -ENRs MUST include a structure enumerating the capabilities offered by the peer in an efficient manner. -The concrete solution is currently undefined. -Proposals include using namespaced bloom filters mapping capabilities to specific protocol IDs supported under that capability. - -### Topic advertisement - -discv5's topic advertisement feature is not expected to be ready for mainnet launch of Phase 0. - -Once this feature is built out and stable, we expect to use topic advertisement as a rendezvous facility for peers on shards. -Until then, the ENR [attestation subnet bitfield](#attestation-subnet-bitfield) will be used for discovery of peers on particular subnets. - # Design decision rationale ## Transport @@ -1408,7 +1391,7 @@ discv5 supports self-certified, flexible peer records (ENRs) and topic-based adv On the other hand, libp2p Kademlia DHT is a fully-fledged DHT protocol/implementations with content routing and storage capabilities, both of which are irrelevant in this context. -We assume that Eth 1.0 nodes will evolve to support discv5. +Eth 1.0 nodes will evolve to support discv5. By sharing the discovery network between Eth 1.0 and 2.0, we benefit from the additive effect on network size that enhances resilience and resistance against certain attacks, to which smaller networks are more vulnerable. From d3d9bbd47908626c573466022321e9f8edcf1eb4 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Oct 2020 17:46:20 -0600 Subject: [PATCH 23/48] add note about coming gs v1.1 params --- specs/phase0/p2p-interface.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 0ef076f79..9a27b54fc 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -211,8 +211,6 @@ including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsu **Gossipsub Parameters** -*Note*: Parameters listed here are subject to a large-scale network feasibility study. - The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) will be used: - `D` (topic stable mesh target count): 6 @@ -225,6 +223,11 @@ The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master - `mcache_gossip` (number of windows to gossip about): 3 - `seen_ttl` (number of heartbeat intervals to retain message IDs): 550 +*Note*: Gossipsub v1.1 introduces a number of +[additional parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.1.md#overview-of-new-parameters) +for peer scoring and other attack mitigations. +These are currently under investigation and will be spec'd and released to mainnet when they are ready. + ### Topics and messages Topics are plain UTF-8 strings and are encoded on the wire as determined by protobuf (gossipsub messages are enveloped in protobuf messages). From d0cf4e7bad4cbfc0a0aefa17b5e9085daa59909a Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 7 Oct 2020 16:52:51 -0700 Subject: [PATCH 24/48] Simplify fork choice spec This change should be non-substantive as any blocks in `blocks` should be descendants (inclusive) of the `store.justified_checkpoint` (refer `get_filtered_block_tree`) so that in `get_head` all blocks considered as potential heads will have `slot > justified_slot`. Considering this condition universally applies, adding the `and ...` arm to the conditional is unnecessary overhead. --- specs/phase0/fork-choice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 018cbec4b..9e7cb4848 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -230,7 +230,7 @@ def get_head(store: Store) -> Root: while True: children = [ root for root in blocks.keys() - if blocks[root].parent_root == head and blocks[root].slot > justified_slot + if blocks[root].parent_root == head ] if len(children) == 0: return head From 663af777376c4a60deaa14bcd18e4a3ba581da63 Mon Sep 17 00:00:00 2001 From: Alex Stokes Date: Wed, 7 Oct 2020 17:11:13 -0700 Subject: [PATCH 25/48] Clean up unused variable for linter --- specs/phase0/fork-choice.md | 1 - 1 file changed, 1 deletion(-) diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 9e7cb4848..e15ecd1ca 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -226,7 +226,6 @@ def get_head(store: Store) -> Root: blocks = get_filtered_block_tree(store) # Execute the LMD-GHOST fork choice head = store.justified_checkpoint.root - justified_slot = compute_start_slot_at_epoch(store.justified_checkpoint.epoch) while True: children = [ root for root in blocks.keys() From 91deacbf9ca7c5fbd2110ca1ef7c2262ad171637 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 7 Oct 2020 18:52:47 -0600 Subject: [PATCH 26/48] double eth1data params --- configs/mainnet/phase0.yaml | 8 ++++---- specs/phase0/beacon-chain.md | 4 ++-- specs/phase0/validator.md | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/configs/mainnet/phase0.yaml b/configs/mainnet/phase0.yaml index 569e5ccb5..95eac0e40 100644 --- a/configs/mainnet/phase0.yaml +++ b/configs/mainnet/phase0.yaml @@ -36,8 +36,8 @@ SAFE_SLOTS_TO_UPDATE_JUSTIFIED: 8 # Validator # --------------------------------------------------------------- -# 2**10 (= 1,024) -ETH1_FOLLOW_DISTANCE: 1024 +# 2**11 (= 2,048) +ETH1_FOLLOW_DISTANCE: 2048 # 2**4 (= 16) TARGET_AGGREGATORS_PER_COMMITTEE: 16 # 2**0 (= 1) @@ -90,8 +90,8 @@ SLOTS_PER_EPOCH: 32 MIN_SEED_LOOKAHEAD: 1 # 2**2 (= 4) epochs 25.6 minutes MAX_SEED_LOOKAHEAD: 4 -# 2**5 (= 32) epochs ~3.4 hours -EPOCHS_PER_ETH1_VOTING_PERIOD: 32 +# 2**6 (= 64) epochs ~6.8 hours +EPOCHS_PER_ETH1_VOTING_PERIOD: 64 # 2**13 (= 8,192) slots ~13 hours SLOTS_PER_HISTORICAL_ROOT: 8192 # 2**8 (= 256) epochs ~27 hours diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index e77bfb08c..eb2160ca2 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -183,7 +183,7 @@ The following values are (non-configurable) constants used throughout the specif | Name | Value | | - | - | -| `ETH1_FOLLOW_DISTANCE` | `uint64(2**10)` (= 1,024) | +| `ETH1_FOLLOW_DISTANCE` | `uint64(2**11)` (= 2,048) | | `MAX_COMMITTEES_PER_SLOT` | `uint64(2**6)` (= 64) | | `TARGET_COMMITTEE_SIZE` | `uint64(2**7)` (= 128) | | `MAX_VALIDATORS_PER_COMMITTEE` | `uint64(2**11)` (= 2,048) | @@ -226,7 +226,7 @@ The following values are (non-configurable) constants used throughout the specif | `MIN_SEED_LOOKAHEAD` | `uint64(2**0)` (= 1) | epochs | 6.4 minutes | | `MAX_SEED_LOOKAHEAD` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | | `MIN_EPOCHS_TO_INACTIVITY_PENALTY` | `uint64(2**2)` (= 4) | epochs | 25.6 minutes | -| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**5)` (= 32) | epochs | ~3.4 hours | +| `EPOCHS_PER_ETH1_VOTING_PERIOD` | `uint64(2**6)` (= 64) | epochs | ~6.8 hours | | `SLOTS_PER_HISTORICAL_ROOT` | `uint64(2**13)` (= 8,192) | slots | ~27 hours | | `MIN_VALIDATOR_WITHDRAWABILITY_DELAY` | `uint64(2**8)` (= 256) | epochs | ~27 hours | | `SHARD_COMMITTEE_PERIOD` | `uint64(2**8)` (= 256) | epochs | ~27 hours | diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 95411f1b6..be9a16755 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -129,7 +129,7 @@ To submit a deposit: ### Process deposit -Deposits cannot be processed into the beacon chain until the Eth1 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~4 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~3.4 hours). Once the requisite Eth1 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated. +Deposits cannot be processed into the beacon chain until the Eth1 block in which they were deposited or any of its descendants is added to the beacon chain `state.eth1_data`. This takes _a minimum_ of `ETH1_FOLLOW_DISTANCE` Eth1 blocks (~8 hours) plus `EPOCHS_PER_ETH1_VOTING_PERIOD` epochs (~6.8 hours). Once the requisite Eth1 data is added, the deposit will normally be added to a beacon chain block and processed into the `state.validators` within an epoch or two. The validator is then in a queue to be activated. ### Validator index From 427c7bb236a86819856d8a9b7af3be09758348c3 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Thu, 8 Oct 2020 10:18:15 -0600 Subject: [PATCH 27/48] fix minor typo in validator guide --- specs/phase0/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 95411f1b6..76dc12142 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -289,7 +289,7 @@ class Eth1Block(Container): Let `get_eth1_data(block: Eth1Block) -> Eth1Data` be the function that returns the Eth1 data for a given Eth1 block. -An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state)` where: +An honest block proposer sets `block.body.eth1_data = get_eth1_vote(state, eth1_chain)` where: ```python def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: From 8fa3efe0d32c45a33e295b768f049d04a1f60f23 Mon Sep 17 00:00:00 2001 From: Andrew Redden Date: Thu, 8 Oct 2020 16:02:43 -0300 Subject: [PATCH 28/48] Update simple-serialize.md Fix the Lighthouse implementation link --- ssz/simple-serialize.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index 607dd0946..4cc590ea2 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -252,7 +252,7 @@ We similarly define "summary types" and "expansion types". For example, [`Beacon | Language | Project | Maintainer | Implementation | |-|-|-|-| | Python | Ethereum 2.0 | Ethereum Foundation | [https://github.com/ethereum/py-ssz](https://github.com/ethereum/py-ssz) | -| Rust | Lighthouse | Sigma Prime | [https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz](https://github.com/sigp/lighthouse/tree/master/eth2/utils/ssz) | +| Rust | Lighthouse | Sigma Prime | [https://github.com/sigp/lighthouse/tree/master/consensus/ssz](https://github.com/sigp/lighthouse/tree/master/consensus/ssz) | | Nim | Nimbus | Status | [https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim](https://github.com/status-im/nim-beacon-chain/blob/master/beacon_chain/ssz.nim) | | Rust | Shasper | ParityTech | [https://github.com/paritytech/shasper/tree/master/utils/ssz](https://github.com/paritytech/shasper/tree/master/utils/ssz) | | TypeScript | Lodestar | ChainSafe Systems | [https://github.com/ChainSafe/ssz-js](https://github.com/ChainSafe/ssz) | From d6ccbd515dbadd1a992bbf66270ab6a900aad176 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Tue, 13 Oct 2020 11:39:10 +0800 Subject: [PATCH 29/48] Fix typo, `messages` -> `message` --- tests/generators/bls/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/generators/bls/main.py b/tests/generators/bls/main.py index 8cdc2a94b..e7e1ccfcf 100644 --- a/tests/generators/bls/main.py +++ b/tests/generators/bls/main.py @@ -272,7 +272,7 @@ def case04_fast_aggregate_verify(): yield f'fast_aggregate_verify_infinity_pubkey', { 'input': { 'pubkeys': [encode_hex(pubkey) for pubkey in pubkeys_with_infinity], - 'messages': encode_hex(SAMPLE_MESSAGE), + 'message': encode_hex(SAMPLE_MESSAGE), 'signature': encode_hex(aggregate_signature), }, 'output': False, From a61c56a6b66c419233476332269f24df899c10f8 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 13 Oct 2020 16:15:35 -0600 Subject: [PATCH 30/48] add a few rewards tests for duplicate style attestations --- .../test_process_rewards_and_penalties.py | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py index 0137ddca4..661a00014 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py +++ b/tests/core/pyspec/eth2spec/test/phase0/epoch_processing/test_process_rewards_and_penalties.py @@ -1,6 +1,7 @@ from eth2spec.test.context import ( spec_state_test, spec_test, with_all_phases, single_phase, + with_phases, PHASE0, with_custom_state, zero_activation_threshold, misc_balances, low_single_balance, @@ -12,6 +13,7 @@ from eth2spec.test.helpers.state import ( from eth2spec.test.helpers.attestations import ( add_attestations_to_state, get_valid_attestation, + sign_attestation, prepare_state_with_attestations, ) from eth2spec.test.helpers.rewards import leaking @@ -278,6 +280,135 @@ def test_duplicate_attestation(spec, state): assert single_state.balances[index] == dup_state.balances[index] +# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged +# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test +@with_phases([PHASE0]) +@spec_state_test +def test_duplicate_participants_different_attestation_1(spec, state): + """ + Same attesters get two different attestations on chain for the same inclusion delay + Earlier attestation (by list order) is correct, later has incorrect head + Note: although these are slashable, they can validly be included + """ + correct_attestation = get_valid_attestation(spec, state, signed=True) + incorrect_attestation = correct_attestation.copy() + incorrect_attestation.data.beacon_block_root = b'\x42' * 32 + sign_attestation(spec, state, incorrect_attestation) + + indexed_attestation = spec.get_indexed_attestation(state, correct_attestation) + participants = get_indexed_attestation_participants(spec, indexed_attestation) + + assert len(participants) > 0 + + single_correct_state = state.copy() + dup_state = state.copy() + + inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot) + add_attestations_to_state(spec, dup_state, [correct_attestation, incorrect_attestation], inclusion_slot) + + next_epoch(spec, single_correct_state) + next_epoch(spec, dup_state) + + # Run non-duplicate inclusion rewards for comparison. Do not yield test vectors + for _ in run_process_rewards_and_penalties(spec, single_correct_state): + pass + + # Output duplicate inclusion to test vectors + yield from run_process_rewards_and_penalties(spec, dup_state) + + for index in participants: + assert state.balances[index] < single_correct_state.balances[index] + assert single_correct_state.balances[index] == dup_state.balances[index] + + +# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged +# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test +@with_phases([PHASE0]) +@spec_state_test +def test_duplicate_participants_different_attestation_2(spec, state): + """ + Same attesters get two different attestations on chain for the same inclusion delay + Earlier attestation (by list order) has incorrect head, later is correct + Note: although these are slashable, they can validly be included + """ + correct_attestation = get_valid_attestation(spec, state, signed=True) + incorrect_attestation = correct_attestation.copy() + incorrect_attestation.data.beacon_block_root = b'\x42' * 32 + sign_attestation(spec, state, incorrect_attestation) + + indexed_attestation = spec.get_indexed_attestation(state, correct_attestation) + participants = get_indexed_attestation_participants(spec, indexed_attestation) + + assert len(participants) > 0 + + single_correct_state = state.copy() + dup_state = state.copy() + + inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot) + add_attestations_to_state(spec, dup_state, [incorrect_attestation, correct_attestation], inclusion_slot) + + next_epoch(spec, single_correct_state) + next_epoch(spec, dup_state) + + # Run non-duplicate inclusion rewards for comparison. Do not yield test vectors + for _ in run_process_rewards_and_penalties(spec, single_correct_state): + pass + + # Output duplicate inclusion to test vectors + yield from run_process_rewards_and_penalties(spec, dup_state) + + for index in participants: + assert state.balances[index] < single_correct_state.balances[index] + # Inclusion delay does not take into account correctness so equal reward + assert single_correct_state.balances[index] == dup_state.balances[index] + + +# TODO: update to all phases when https://github.com/ethereum/eth2.0-specs/pull/2024 is merged +# Currently disabled for Phase 1+ due to the mechanics of on-time-attestations complicating what should be a simple test +@with_phases([PHASE0]) +@spec_state_test +def test_duplicate_participants_different_attestation_3(spec, state): + """ + Same attesters get two different attestations on chain for *different* inclusion delay + Earlier attestation (by list order) has incorrect head, later is correct + Note: although these are slashable, they can validly be included + """ + correct_attestation = get_valid_attestation(spec, state, signed=True) + incorrect_attestation = correct_attestation.copy() + incorrect_attestation.data.beacon_block_root = b'\x42' * 32 + sign_attestation(spec, state, incorrect_attestation) + + indexed_attestation = spec.get_indexed_attestation(state, correct_attestation) + participants = get_indexed_attestation_participants(spec, indexed_attestation) + + assert len(participants) > 0 + + single_correct_state = state.copy() + dup_state = state.copy() + + inclusion_slot = state.slot + spec.MIN_ATTESTATION_INCLUSION_DELAY + add_attestations_to_state(spec, single_correct_state, [correct_attestation], inclusion_slot) + add_attestations_to_state(spec, dup_state, [incorrect_attestation], inclusion_slot) + add_attestations_to_state(spec, dup_state, [correct_attestation], inclusion_slot + 1) + + next_epoch(spec, single_correct_state) + next_epoch(spec, dup_state) + + # Run non-duplicate inclusion rewards for comparison. Do not yield test vectors + for _ in run_process_rewards_and_penalties(spec, single_correct_state): + pass + + # Output duplicate inclusion to test vectors + yield from run_process_rewards_and_penalties(spec, dup_state) + + for index in participants: + assert state.balances[index] < single_correct_state.balances[index] + # Inclusion delay does not take into account correctness so equal reward + assert single_correct_state.balances[index] == dup_state.balances[index] + + @with_all_phases @spec_state_test # Case when some eligible attestations are slashed. Modifies attesting_balance and consequently rewards/penalties. From c17a95a175d7ef149572e36b823eda28163f707b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 14 Oct 2020 17:33:14 -0600 Subject: [PATCH 31/48] add note about how slashings and exits can interact. add test --- specs/phase0/validator.md | 4 ++ .../eth2spec/test/helpers/voluntary_exits.py | 15 ++++++ .../test/phase0/sanity/test_blocks.py | 17 +----- .../phase0/sanity/test_multi_operations.py | 53 +++++++++++++++++++ 4 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index be9a16755..82bd4cf6e 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -358,6 +358,10 @@ The `proof` for each deposit must be constructed against the deposit root contai Up to `MAX_VOLUNTARY_EXITS`, [`VoluntaryExit`](./beacon-chain.md#voluntaryexit) objects can be included in the `block`. The exits must satisfy the verification conditions found in [exits processing](./beacon-chain.md#voluntary-exits). +*Note*: If a slashing for a validator is included in the same block as a +voluntary exit, the voluntary exit will fail and cause the block to be invalid +due to the slashing being processed first. Implementers must take heed of this +operation interaction when packing blocks. #### Packaging into a `SignedBeaconBlock` diff --git a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py index 55310ef7d..28232cc23 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/voluntary_exits.py @@ -1,4 +1,19 @@ from eth2spec.utils import bls +from eth2spec.test.helpers.keys import privkeys + + +def prepare_signed_exits(spec, state, indices): + domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) + + def create_signed_exit(index): + exit = spec.VoluntaryExit( + epoch=spec.get_current_epoch(state), + validator_index=index, + ) + signing_root = spec.compute_signing_root(exit, domain) + return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root)) + + return [create_signed_exit(index) for index in indices] def sign_voluntary_exit(spec, state, voluntary_exit, privkey): diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 721d68add..dc56965ac 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -9,7 +9,7 @@ from eth2spec.test.helpers.block import ( sign_block, transition_unsigned_block, ) -from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.keys import pubkeys from eth2spec.test.helpers.attester_slashings import ( get_valid_attester_slashing_by_indices, get_valid_attester_slashing, @@ -18,6 +18,7 @@ from eth2spec.test.helpers.attester_slashings import ( from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing, check_proposer_slashing_effect from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee from eth2spec.test.context import ( @@ -794,20 +795,6 @@ def test_attestation(spec, state): assert spec.hash_tree_root(state.previous_epoch_attestations) == pre_current_attestations_root -def prepare_signed_exits(spec, state, indices): - domain = spec.get_domain(state, spec.DOMAIN_VOLUNTARY_EXIT) - - def create_signed_exit(index): - exit = spec.VoluntaryExit( - epoch=spec.get_current_epoch(state), - validator_index=index, - ) - signing_root = spec.compute_signing_root(exit, domain) - return spec.SignedVoluntaryExit(message=exit, signature=bls.Sign(privkeys[index], signing_root)) - - return [create_signed_exit(index) for index in indices] - - # In phase1 a committee is computed for SHARD_COMMITTEE_PERIOD slots ago, # exceeding the minimal-config randao mixes memory size. # Applies to all voluntary-exit sanity block tests. diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py new file mode 100644 index 000000000..1e1ed42f5 --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py @@ -0,0 +1,53 @@ +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits + +from eth2spec.test.context import ( + spec_state_test, + with_all_phases, +) + + +def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=slash_index, signed_1=True, signed_2=True) + signed_exit = prepare_signed_exits(spec, state, [exit_index])[0] + + block.body.proposer_slashings.append(proposer_slashing) + block.body.voluntary_exits.append(signed_exit) + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid)) + + yield 'blocks', [signed_block] + + if valid: + yield 'post', state + else: + yield 'post', None + + +@with_all_phases +@spec_state_test +def test_slash_and_exit_same_index(spec, state): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + run_slash_and_exit(spec, state, validator_index, validator_index, valid=False) + + +@with_all_phases +@spec_state_test +def test_slash_and_exit_diff_index(spec, state): + slash_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + exit_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-2] + run_slash_and_exit(spec, state, slash_index, exit_index) From aab58e47008e161336dd591bd52e3c39c046a288 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 14 Oct 2020 17:36:10 -0600 Subject: [PATCH 32/48] update sanity generators to generate the oepration block tests as well --- .../eth2spec/test/phase0/sanity/test_multi_operations.py | 4 ++-- tests/generators/sanity/main.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py index 1e1ed42f5..3c14a7d9d 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py @@ -42,7 +42,7 @@ def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): @spec_state_test def test_slash_and_exit_same_index(spec, state): validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] - run_slash_and_exit(spec, state, validator_index, validator_index, valid=False) + yield from run_slash_and_exit(spec, state, validator_index, validator_index, valid=False) @with_all_phases @@ -50,4 +50,4 @@ def test_slash_and_exit_same_index(spec, state): def test_slash_and_exit_diff_index(spec, state): slash_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] exit_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-2] - run_slash_and_exit(spec, state, slash_index, exit_index) + yield from run_slash_and_exit(spec, state, slash_index, exit_index) diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index 83166f0cf..afd255dcb 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -34,6 +34,7 @@ def create_provider(fork_name: str, handler_name: str, if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.sanity.test_' + key for key in [ 'blocks', + 'multi_operations', 'slots', ]} phase_1_mods = {**{key: 'eth2spec.test.phase1.sanity.test_' + key for key in [ From 7fb9226ec6fb95a6ccc615d9b0071652199f4013 Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 15 Oct 2020 15:58:26 +0800 Subject: [PATCH 33/48] Make `state_transition` not return post state; the given pre state should have been mutated to post state. --- specs/phase0/beacon-chain.md | 4 +--- specs/phase0/fork-choice.md | 3 ++- specs/phase0/validator.md | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index eb2160ca2..84e9af9dd 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1202,7 +1202,7 @@ Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. The post-state corresponding to a pre-state `state` and a signed block `signed_block` is defined as `state_transition(state, signed_block)`. State transitions that trigger an unhandled exception (e.g. a failed `assert` or an out-of-range list access) are considered invalid. State transitions that cause a `uint64` overflow or underflow are also considered invalid. ```python -def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> BeaconState: +def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, validate_result: bool=True) -> None: block = signed_block.message # Process slots (including those with no blocks) since block process_slots(state, block.slot) @@ -1214,8 +1214,6 @@ def state_transition(state: BeaconState, signed_block: SignedBeaconBlock, valida # Verify state root if validate_result: assert block.state_root == hash_tree_root(state) - # Return post-state - return state ``` ```python diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index 018cbec4b..50bd298e9 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -355,7 +355,8 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: assert get_ancestor(store, block.parent_root, finalized_slot) == store.finalized_checkpoint.root # Check the block is valid and compute the post-state - state = state_transition(pre_state, signed_block, True) + state = pre_state.copy() + state_transition(state, signed_block, True) # Add new block to the store store.blocks[hash_tree_root(block)] = block # Add new state for this block to the store diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index be9a16755..97e0e4901 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -372,7 +372,7 @@ It is useful to be able to run a state transition function (working on a copy of def compute_new_state_root(state: BeaconState, block: BeaconBlock) -> Root: temp_state: BeaconState = state.copy() signed_block = SignedBeaconBlock(message=block) - temp_state = state_transition(temp_state, signed_block, validate_result=False) + state_transition(temp_state, signed_block, validate_result=False) return hash_tree_root(temp_state) ``` From f1e4b3c88bfff6355b5ec2143f274c1c122f350c Mon Sep 17 00:00:00 2001 From: Hsiao-Wei Wang Date: Thu, 15 Oct 2020 16:14:01 +0800 Subject: [PATCH 34/48] Refactor shard_state_transition --- specs/phase1/shard-transition.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/specs/phase1/shard-transition.md b/specs/phase1/shard-transition.md index df521e451..f3a1f83ce 100644 --- a/specs/phase1/shard-transition.md +++ b/specs/phase1/shard-transition.md @@ -72,14 +72,13 @@ The post-state corresponding to a pre-state `shard_state` and a signed block `si def shard_state_transition(shard_state: ShardState, signed_block: SignedShardBlock, beacon_parent_state: BeaconState, - validate_result: bool = True) -> ShardState: + validate_result: bool = True) -> None: assert verify_shard_block_message(beacon_parent_state, shard_state, signed_block.message) if validate_result: assert verify_shard_block_signature(beacon_parent_state, signed_block) process_shard_block(shard_state, signed_block.message) - return shard_state ``` ```python From a34970f8a3a1ee32e06b845bf3de6d70e69090d0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Fri, 16 Oct 2020 08:27:28 -0600 Subject: [PATCH 35/48] move op tests to test_blocks --- .../test/phase0/sanity/test_blocks.py | 45 ++++++++++++++++ .../phase0/sanity/test_multi_operations.py | 53 ------------------- tests/generators/sanity/main.py | 1 - 3 files changed, 45 insertions(+), 54 deletions(-) delete mode 100644 tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index dc56965ac..44aabdb76 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -882,6 +882,51 @@ def test_multiple_different_validator_exits_same_block(spec, state): assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH +def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): + """ + Helper function to run a test that slashes and exits two validators + """ + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=slash_index, signed_1=True, signed_2=True) + signed_exit = prepare_signed_exits(spec, state, [exit_index])[0] + + block.body.proposer_slashings.append(proposer_slashing) + block.body.voluntary_exits.append(signed_exit) + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid)) + + yield 'blocks', [signed_block] + + if valid: + yield 'post', state + else: + yield 'post', None + + +@with_all_phases +@spec_state_test +@disable_process_reveal_deadlines +def test_slash_and_exit_same_index(spec, state): + validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + yield from run_slash_and_exit(spec, state, validator_index, validator_index, valid=False) + + +@with_all_phases +@spec_state_test +@disable_process_reveal_deadlines +def test_slash_and_exit_diff_index(spec, state): + slash_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] + exit_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-2] + yield from run_slash_and_exit(spec, state, slash_index, exit_index) + + @with_all_phases @spec_state_test def test_balance_driven_status_transitions(spec, state): diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py deleted file mode 100644 index 3c14a7d9d..000000000 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_multi_operations.py +++ /dev/null @@ -1,53 +0,0 @@ -from eth2spec.test.helpers.state import ( - state_transition_and_sign_block, -) -from eth2spec.test.helpers.block import ( - build_empty_block_for_next_slot, -) -from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing -from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits - -from eth2spec.test.context import ( - spec_state_test, - with_all_phases, -) - - -def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): - # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit - state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - - yield 'pre', state - - block = build_empty_block_for_next_slot(spec, state) - - proposer_slashing = get_valid_proposer_slashing( - spec, state, slashed_index=slash_index, signed_1=True, signed_2=True) - signed_exit = prepare_signed_exits(spec, state, [exit_index])[0] - - block.body.proposer_slashings.append(proposer_slashing) - block.body.voluntary_exits.append(signed_exit) - - signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid)) - - yield 'blocks', [signed_block] - - if valid: - yield 'post', state - else: - yield 'post', None - - -@with_all_phases -@spec_state_test -def test_slash_and_exit_same_index(spec, state): - validator_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] - yield from run_slash_and_exit(spec, state, validator_index, validator_index, valid=False) - - -@with_all_phases -@spec_state_test -def test_slash_and_exit_diff_index(spec, state): - slash_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-1] - exit_index = spec.get_active_validator_indices(state, spec.get_current_epoch(state))[-2] - yield from run_slash_and_exit(spec, state, slash_index, exit_index) diff --git a/tests/generators/sanity/main.py b/tests/generators/sanity/main.py index afd255dcb..83166f0cf 100644 --- a/tests/generators/sanity/main.py +++ b/tests/generators/sanity/main.py @@ -34,7 +34,6 @@ def create_provider(fork_name: str, handler_name: str, if __name__ == "__main__": phase_0_mods = {key: 'eth2spec.test.phase0.sanity.test_' + key for key in [ 'blocks', - 'multi_operations', 'slots', ]} phase_1_mods = {**{key: 'eth2spec.test.phase1.sanity.test_' + key for key in [ From 73cd1a84fd9b460f7b6479766706f58fea2d7aa3 Mon Sep 17 00:00:00 2001 From: Alon Muroch Date: Tue, 20 Oct 2020 12:00:40 +0300 Subject: [PATCH 36/48] added best practices section according to https://github.com/ethereum/eth2.0-specs/issues/2085 --- specs/phase0/validator.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 76dc12142..fb75db2e3 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -604,3 +604,11 @@ Specifically, when signing an `Attestation`, a validator should perform the foll 2. Generate and broadcast attestation. If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing. + +## Protection best practices +A validator client should be considered standalone and should consider the node it's connected to as un-trusted. This means that the validator client should protect: +1) Private keys - private keys should be protected from ever being exported out accidentally or by an attacker. Attestations and blocks should be signed internally in the process itself, keys should always be encrypted on disk. +2) Slashing - before a validator client signs an attestation/ block it should validate the data, check against a local slashing db (do not sign slashable attestation/ block) and update its internal slashing db with the new signed object. +3) Recovered validator - Recovering a validator from a private key will result in an empty local slashing db. A best practice is to import (from a trusted source) that validator's attestation history. +4) Far future signing requests - A validator client can be requested to sign a far into the future attestation, resulting in a valid non slashable request. If the validator client signs it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity leak and potential slashing. +A validator client should prevent itself from signing such requests by estimating the current slot with some deviation. From fe5e2f6a8bf31c3d17fc9ef3dbf0641e5ec309f5 Mon Sep 17 00:00:00 2001 From: Alon Muroch Date: Tue, 20 Oct 2020 12:07:54 +0300 Subject: [PATCH 37/48] table of content --- specs/phase0/validator.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index fb75db2e3..b0065dd81 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -65,6 +65,7 @@ - [How to avoid slashing](#how-to-avoid-slashing) - [Proposer slashing](#proposer-slashing) - [Attester slashing](#attester-slashing) +- [Protection best practices](#protection-best-practices) From f7a9493ca0ec1ad248536fc04e50197dd6c8cef0 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 20 Oct 2020 16:55:28 -0600 Subject: [PATCH 38/48] multi-op tests --- .../test/helpers/attester_slashings.py | 14 +- .../pyspec/eth2spec/test/helpers/deposits.py | 5 +- .../eth2spec/test/helpers/multi_operations.py | 155 ++++++++++++++++++ .../test/phase0/sanity/test_blocks.py | 57 +++---- 4 files changed, 198 insertions(+), 33 deletions(-) create mode 100644 tests/core/pyspec/eth2spec/test/helpers/multi_operations.py diff --git a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py index 43763895f..06e904805 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py +++ b/tests/core/pyspec/eth2spec/test/helpers/attester_slashings.py @@ -1,8 +1,11 @@ from eth2spec.test.helpers.attestations import get_valid_attestation, sign_attestation, sign_indexed_attestation -def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False): - attestation_1 = get_valid_attestation(spec, state, signed=signed_1) +def get_valid_attester_slashing(spec, state, slot=None, signed_1=False, signed_2=False, filter_participant_set=None): + attestation_1 = get_valid_attestation( + spec, state, + slot=slot, signed=signed_1, filter_participant_set=filter_participant_set + ) attestation_2 = attestation_1.copy() attestation_2.data.target.root = b'\x01' * 32 @@ -16,14 +19,17 @@ def get_valid_attester_slashing(spec, state, signed_1=False, signed_2=False): ) -def get_valid_attester_slashing_by_indices(spec, state, indices_1, indices_2=None, signed_1=False, signed_2=False): +def get_valid_attester_slashing_by_indices(spec, state, + indices_1, indices_2=None, + slot=None, + signed_1=False, signed_2=False): if indices_2 is None: indices_2 = indices_1 assert indices_1 == sorted(indices_1) assert indices_2 == sorted(indices_2) - attester_slashing = get_valid_attester_slashing(spec, state) + attester_slashing = get_valid_attester_slashing(spec, state, slot=slot) attester_slashing.attestation_1.attesting_indices = indices_1 attester_slashing.attestation_2.attesting_indices = indices_2 diff --git a/tests/core/pyspec/eth2spec/test/helpers/deposits.py b/tests/core/pyspec/eth2spec/test/helpers/deposits.py index 9f7d96981..48057a1d9 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/deposits.py +++ b/tests/core/pyspec/eth2spec/test/helpers/deposits.py @@ -56,7 +56,10 @@ 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)) tree = calc_merkle_tree_from_leaves(tuple([d.hash_tree_root() for d in deposit_data_list])) - proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [(index + 1).to_bytes(32, 'little')] + proof = ( + list(get_merkle_proof(tree, item_index=index, tree_len=32)) + + [len(deposit_data_list).to_bytes(32, 'little')] + ) leaf = deposit_data.hash_tree_root() assert spec.is_valid_merkle_branch(leaf, proof, spec.DEPOSIT_CONTRACT_TREE_DEPTH + 1, index, root) deposit = spec.Deposit(proof=proof, data=deposit_data) diff --git a/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py new file mode 100644 index 000000000..e690467bd --- /dev/null +++ b/tests/core/pyspec/eth2spec/test/helpers/multi_operations.py @@ -0,0 +1,155 @@ +from random import Random + +from eth2spec.test.helpers.keys import privkeys, pubkeys +from eth2spec.test.helpers.state import ( + state_transition_and_sign_block, +) +from eth2spec.test.helpers.block import ( + build_empty_block_for_next_slot, +) +from eth2spec.test.helpers.proposer_slashings import get_valid_proposer_slashing +from eth2spec.test.helpers.attester_slashings import get_valid_attester_slashing_by_indices +from eth2spec.test.helpers.attestations import get_valid_attestation +from eth2spec.test.helpers.deposits import build_deposit, deposit_from_context +from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits + + +def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): + """ + Helper function to run a test that slashes and exits two validators + """ + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + yield 'pre', state + + block = build_empty_block_for_next_slot(spec, state) + + proposer_slashing = get_valid_proposer_slashing( + spec, state, slashed_index=slash_index, signed_1=True, signed_2=True) + signed_exit = prepare_signed_exits(spec, state, [exit_index])[0] + + block.body.proposer_slashings.append(proposer_slashing) + block.body.voluntary_exits.append(signed_exit) + + signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid)) + + yield 'blocks', [signed_block] + + if not valid: + yield 'post', None + return + + yield 'post', state + + +def get_random_proposer_slashings(spec, state, rng): + num_slashings = rng.randrange(spec.MAX_PROPOSER_SLASHINGS) + indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() + slashings = [ + get_valid_proposer_slashing( + spec, state, + slashed_index=indices.pop(rng.randrange(len(indices))), signed_1=True, signed_2=True, + ) + for _ in range(num_slashings) + ] + return slashings + + +def get_random_attester_slashings(spec, state, rng): + num_slashings = rng.randrange(spec.MAX_ATTESTER_SLASHINGS) + indices = spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy() + slot_range = list(range(state.slot - spec.SLOTS_PER_HISTORICAL_ROOT + 1, state.slot)) + slashings = [ + get_valid_attester_slashing_by_indices( + spec, state, + sorted([indices.pop(rng.randrange(len(indices))) for _ in range(rng.randrange(1, 4))]), + slot=slot_range.pop(rng.randrange(len(slot_range))), + signed_1=True, signed_2=True, + ) + for _ in range(num_slashings) + ] + return slashings + + +def get_random_attestations(spec, state, rng): + num_attestations = rng.randrange(spec.MAX_ATTESTATIONS) + + attestations = [ + get_valid_attestation( + spec, state, + slot=rng.randrange(state.slot - spec.SLOTS_PER_EPOCH + 1, state.slot), + signed=True, + ) + for _ in range(num_attestations) + ] + return attestations + + +def prepare_state_and_get_random_deposits(spec, state, rng): + num_deposits = rng.randrange(spec.MAX_DEPOSITS) + + deposit_data_leaves = [spec.DepositData() for _ in range(len(state.validators))] + deposits = [] + + # First build deposit data leaves + for i in range(num_deposits): + index = len(state.validators) + i + _, root, deposit_data_leaves = build_deposit( + spec, + deposit_data_leaves, + pubkeys[index], + privkeys[index], + spec.MAX_EFFECTIVE_BALANCE, + withdrawal_credentials=b'\x00' * 32, + signed=True, + ) + + state.eth1_data.deposit_root = root + state.eth1_data.deposit_count += num_deposits + + # Then for that context, build deposits/proofs + for i in range(num_deposits): + index = len(state.validators) + i + deposit, _, _ = deposit_from_context(spec, deposit_data_leaves, index) + deposits.append(deposit) + + return deposits + + +def get_random_voluntary_exits(spec, state, to_be_slashed_indices, rng): + num_exits = rng.randrange(spec.MAX_VOLUNTARY_EXITS) + indices = set(spec.get_active_validator_indices(state, spec.get_current_epoch(state)).copy()) + eligible_indices = indices - to_be_slashed_indices + exit_indices = [eligible_indices.pop() for _ in range(num_exits)] + return prepare_signed_exits(spec, state, exit_indices) + + +def run_test_full_random_operations(spec, state, rng=Random(2080)): + # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit + state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH + + # prepare state for deposits before building block + deposits = prepare_state_and_get_random_deposits(spec, state, rng) + + block = build_empty_block_for_next_slot(spec, state) + block.body.proposer_slashings = get_random_proposer_slashings(spec, state, rng) + block.body.attester_slashings = get_random_attester_slashings(spec, state, rng) + block.body.attestations = get_random_attestations(spec, state, rng) + block.body.deposits = deposits + + # cannot include to be slashed indices as exits + slashed_indices = set([ + slashing.signed_header_1.message.proposer_index + for slashing in block.body.proposer_slashings + ]) + for attester_slashing in block.body.attester_slashings: + slashed_indices = slashed_indices.union(attester_slashing.attestation_1.attesting_indices) + slashed_indices = slashed_indices.union(attester_slashing.attestation_2.attesting_indices) + block.body.voluntary_exits = get_random_voluntary_exits(spec, state, slashed_indices, rng) + + signed_block = state_transition_and_sign_block(spec, state, block) + + yield 'pre', state + yield 'blocks', [signed_block] + yield 'post', state diff --git a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py index 44aabdb76..358fd5211 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py +++ b/tests/core/pyspec/eth2spec/test/phase0/sanity/test_blocks.py @@ -1,3 +1,4 @@ +from random import Random from eth2spec.utils import bls from eth2spec.test.helpers.state import ( @@ -20,6 +21,10 @@ from eth2spec.test.helpers.attestations import get_valid_attestation from eth2spec.test.helpers.deposits import prepare_state_and_deposit from eth2spec.test.helpers.voluntary_exits import prepare_signed_exits from eth2spec.test.helpers.shard_transitions import get_shard_transition_of_committee +from eth2spec.test.helpers.multi_operations import ( + run_slash_and_exit, + run_test_full_random_operations, +) from eth2spec.test.context import ( PHASE0, PHASE1, MINIMAL, @@ -882,34 +887,6 @@ def test_multiple_different_validator_exits_same_block(spec, state): assert state.validators[index].exit_epoch < spec.FAR_FUTURE_EPOCH -def run_slash_and_exit(spec, state, slash_index, exit_index, valid=True): - """ - Helper function to run a test that slashes and exits two validators - """ - # move state forward SHARD_COMMITTEE_PERIOD epochs to allow for exit - state.slot += spec.SHARD_COMMITTEE_PERIOD * spec.SLOTS_PER_EPOCH - - yield 'pre', state - - block = build_empty_block_for_next_slot(spec, state) - - proposer_slashing = get_valid_proposer_slashing( - spec, state, slashed_index=slash_index, signed_1=True, signed_2=True) - signed_exit = prepare_signed_exits(spec, state, [exit_index])[0] - - block.body.proposer_slashings.append(proposer_slashing) - block.body.voluntary_exits.append(signed_exit) - - signed_block = state_transition_and_sign_block(spec, state, block, expect_fail=(not valid)) - - yield 'blocks', [signed_block] - - if valid: - yield 'post', state - else: - yield 'post', None - - @with_all_phases @spec_state_test @disable_process_reveal_deadlines @@ -1045,3 +1022,27 @@ def test_eth1_data_votes_no_consensus(spec, state): yield 'blocks', blocks yield 'post', state + + +@with_phases([PHASE0]) +@spec_state_test +def test_full_random_operations_0(spec, state): + yield from run_test_full_random_operations(spec, state, rng=Random(2020)) + + +@with_phases([PHASE0]) +@spec_state_test +def test_full_random_operations_1(spec, state): + yield from run_test_full_random_operations(spec, state, rng=Random(2021)) + + +@with_phases([PHASE0]) +@spec_state_test +def test_full_random_operations_2(spec, state): + yield from run_test_full_random_operations(spec, state, rng=Random(2022)) + + +@with_phases([PHASE0]) +@spec_state_test +def test_full_random_operations_3(spec, state): + yield from run_test_full_random_operations(spec, state, rng=Random(2023)) From 0835c78b56604098ac663e0030ec093e6c73ab4a Mon Sep 17 00:00:00 2001 From: Alon Muroch Date: Tue, 27 Oct 2020 08:38:54 +0200 Subject: [PATCH 39/48] Apply suggestions from Dankrad's code review Co-authored-by: dankrad --- specs/phase0/validator.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index b0065dd81..4a98a0518 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -607,9 +607,9 @@ Specifically, when signing an `Attestation`, a validator should perform the foll If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing. ## Protection best practices -A validator client should be considered standalone and should consider the node it's connected to as un-trusted. This means that the validator client should protect: -1) Private keys - private keys should be protected from ever being exported out accidentally or by an attacker. Attestations and blocks should be signed internally in the process itself, keys should always be encrypted on disk. -2) Slashing - before a validator client signs an attestation/ block it should validate the data, check against a local slashing db (do not sign slashable attestation/ block) and update its internal slashing db with the new signed object. -3) Recovered validator - Recovering a validator from a private key will result in an empty local slashing db. A best practice is to import (from a trusted source) that validator's attestation history. -4) Far future signing requests - A validator client can be requested to sign a far into the future attestation, resulting in a valid non slashable request. If the validator client signs it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity leak and potential slashing. -A validator client should prevent itself from signing such requests by estimating the current slot with some deviation. +A validator client should be considered standalone and should consider the beacon node as untrusted. This means that the validator client should protect: +1) Private keys -- private keys should be protected from being exported accidentally or by an attacker. +2) Slashing -- before a validator client signs a message it should validate the data, check it against a local slashing database (do not sign a slashable attestation or block) and update its internal slashing database with the newly signed object. +3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. +4) Far future signing requests -- A validator client can be requested to sign a far into the future attestation, resulting in a valid non-slashable request. If the validator client signs this message, it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity leak and potential ejection due to low balance. +A validator client should prevent itself from signing such requests by: a) keeping a local time clock if possible and following best practices to stop time server attacks and b) refusing to sign, by default, any message that has a large (>6h) gap from the current slashing protection database indicated a time "jump" or a long offline event. The administrator can manually override this protection to restart the validator after a genuine long offline event. From b356f52c5c3c300b8881ccac8ef4cb30ce69af3c Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 28 Oct 2020 17:54:46 -0600 Subject: [PATCH 40/48] ensure aggregate attestation's slot and target are consistent --- specs/phase0/p2p-interface.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 9a27b54fc..df9de0d70 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -333,6 +333,8 @@ The following validations MUST pass before forwarding the `signed_aggregate_and_ - _[IGNORE]_ `aggregate.data.slot` is within the last `ATTESTATION_PROPAGATION_SLOT_RANGE` slots (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance) -- i.e. `aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot` (a client MAY queue future aggregates for processing at the appropriate slot). +- _[REJECT]_ The aggregate attestation's epoch matches its target -- i.e. `aggregate.data.target.epoch == + compute_epoch_at_slot(aggregate.data.slot)` - _[IGNORE]_ The valid aggregate attestation defined by `hash_tree_root(aggregate)` has _not_ already been seen (via aggregate gossip, within a verified block, or through the creation of an equivalent aggregate locally). - _[IGNORE]_ The `aggregate` is the first valid aggregate received for the aggregator From a8c1d21589786c4dc6ead3a2c25853d0a1722027 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 3 Nov 2020 15:45:18 -0600 Subject: [PATCH 41/48] add attestation test for off by one committee index --- .../test_process_attestation.py | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py index 28fd9ac9e..59012934a 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py @@ -135,20 +135,44 @@ def test_wrong_index_for_committee_signature(spec, state): yield from run_attestation_processing(spec, state, attestation, False) -@with_all_phases -@spec_state_test -@never_bls -def test_wrong_index_for_slot(spec, state): +def reduce_state_committee_count_from_max(spec, state): + """ + Modified ``state`` to ensure that it has fewer committees at each slot than ``MAX_COMMITTEES_PER_SLOT`` + """ while spec.get_committee_count_per_slot(state, spec.get_current_epoch(state)) >= spec.MAX_COMMITTEES_PER_SLOT: state.validators = state.validators[:len(state.validators) // 2] state.balances = state.balances[:len(state.balances) // 2] - index = spec.MAX_COMMITTEES_PER_SLOT - 1 + +@with_all_phases +@spec_state_test +@never_bls +def test_wrong_index_for_slot_0(spec, state): + reduce_state_committee_count_from_max(spec, state) attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - attestation.data.index = index + # Invalid index: current committees per slot is less than the max + attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT - 1 + + yield from run_attestation_processing(spec, state, attestation, False) + + +@with_all_phases +@spec_state_test +@never_bls +def test_wrong_index_for_slot_1(spec, state): + reduce_state_committee_count_from_max(spec, state) + + current_epoch = spec.get_current_epoch(state) + committee_count = spec.get_committee_count_per_slot(state, current_epoch) + + attestation = get_valid_attestation(spec, state, index=0, signed=True) + next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) + + # Invalid index: off by one + attestation.data.index = committee_count yield from run_attestation_processing(spec, state, attestation, False) @@ -160,7 +184,7 @@ def test_invalid_index(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - # off by one (with respect to valid range) on purpose + # Index index: off by one (with respect to valid range) on purpose attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT yield from run_attestation_processing(spec, state, attestation, False) From 276ca6d4d345ccc222a40e386e24159d6da4c39b Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 3 Nov 2020 16:26:46 -0600 Subject: [PATCH 42/48] pr feedback Co-authored-by: Diederik Loerakker --- .../test/phase0/block_processing/test_process_attestation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py index 59012934a..b31cf167c 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py +++ b/tests/core/pyspec/eth2spec/test/phase0/block_processing/test_process_attestation.py @@ -168,7 +168,7 @@ def test_wrong_index_for_slot_1(spec, state): current_epoch = spec.get_current_epoch(state) committee_count = spec.get_committee_count_per_slot(state, current_epoch) - attestation = get_valid_attestation(spec, state, index=0, signed=True) + attestation = get_valid_attestation(spec, state, index=0) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) # Invalid index: off by one @@ -184,7 +184,7 @@ def test_invalid_index(spec, state): attestation = get_valid_attestation(spec, state) next_slots(spec, state, spec.MIN_ATTESTATION_INCLUSION_DELAY) - # Index index: off by one (with respect to valid range) on purpose + # Invalid index: off by one (with respect to valid range) on purpose attestation.data.index = spec.MAX_COMMITTEES_PER_SLOT yield from run_attestation_processing(spec, state, attestation, False) From 15270b793309b152000a4a7d4de2752c3a3de706 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Tue, 3 Nov 2020 17:15:36 -0600 Subject: [PATCH 43/48] bump VERSION.txt to 1.0.0 --- tests/core/pyspec/eth2spec/VERSION.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/pyspec/eth2spec/VERSION.txt b/tests/core/pyspec/eth2spec/VERSION.txt index d61567cd1..afaf360d3 100644 --- a/tests/core/pyspec/eth2spec/VERSION.txt +++ b/tests/core/pyspec/eth2spec/VERSION.txt @@ -1 +1 @@ -0.12.3 \ No newline at end of file +1.0.0 \ No newline at end of file From 82cdbba70307b0c7bf485eb67990d10d7cbb4db6 Mon Sep 17 00:00:00 2001 From: Age Manning Date: Wed, 4 Nov 2020 11:27:35 +1100 Subject: [PATCH 44/48] Increase mesh degree --- specs/phase0/p2p-interface.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index bfb61b22e..2b196b8ec 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -214,8 +214,8 @@ including the [gossipsub v1.1](https://github.com/libp2p/specs/blob/master/pubsu The following gossipsub [parameters](https://github.com/libp2p/specs/blob/master/pubsub/gossipsub/gossipsub-v1.0.md#parameters) will be used: -- `D` (topic stable mesh target count): 6 -- `D_low` (topic stable mesh low watermark): 5 +- `D` (topic stable mesh target count): 8 +- `D_low` (topic stable mesh low watermark): 6 - `D_high` (topic stable mesh high watermark): 12 - `D_lazy` (gossip target): 6 - `heartbeat_interval` (frequency of heartbeat, seconds): 0.7 From 5b95219d18b8aa521511340f62d0912c07dc62a7 Mon Sep 17 00:00:00 2001 From: Alon Muroch Date: Wed, 4 Nov 2020 15:19:41 +0200 Subject: [PATCH 45/48] Apply suggestions from code review Co-authored-by: Danny Ryan --- specs/phase0/validator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 4a98a0518..4ce4be63a 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -610,6 +610,6 @@ If the software crashes at some point within this routine, then when the validat A validator client should be considered standalone and should consider the beacon node as untrusted. This means that the validator client should protect: 1) Private keys -- private keys should be protected from being exported accidentally or by an attacker. 2) Slashing -- before a validator client signs a message it should validate the data, check it against a local slashing database (do not sign a slashable attestation or block) and update its internal slashing database with the newly signed object. -3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. +3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. See [EIP 3076](https://github.com/ethereum/EIPs/pull/3076/files) for a standard slashing interchange format. 4) Far future signing requests -- A validator client can be requested to sign a far into the future attestation, resulting in a valid non-slashable request. If the validator client signs this message, it will result in it blocking itself from attesting any other attestation until the beacon-chain reaches that far into the future epoch. This will result in an inactivity leak and potential ejection due to low balance. A validator client should prevent itself from signing such requests by: a) keeping a local time clock if possible and following best practices to stop time server attacks and b) refusing to sign, by default, any message that has a large (>6h) gap from the current slashing protection database indicated a time "jump" or a long offline event. The administrator can manually override this protection to restart the validator after a genuine long offline event. From 6996a897b5dd170e2a8d6d33cd41ea7cabfc234c Mon Sep 17 00:00:00 2001 From: Alon Muroch Date: Wed, 4 Nov 2020 15:29:06 +0200 Subject: [PATCH 46/48] Apply suggestions from code review Co-authored-by: Danny Ryan --- specs/phase0/validator.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 4ce4be63a..902b299fa 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -607,7 +607,9 @@ Specifically, when signing an `Attestation`, a validator should perform the foll If the software crashes at some point within this routine, then when the validator comes back online, the hard disk has the record of the *potentially* signed/broadcast attestation and can effectively avoid slashing. ## Protection best practices -A validator client should be considered standalone and should consider the beacon node as untrusted. This means that the validator client should protect: + +A validator client should be considered standalone and should consider the beacon node as untrusted. This means that the validator client should protect: + 1) Private keys -- private keys should be protected from being exported accidentally or by an attacker. 2) Slashing -- before a validator client signs a message it should validate the data, check it against a local slashing database (do not sign a slashable attestation or block) and update its internal slashing database with the newly signed object. 3) Recovered validator -- Recovering a validator from a private key will result in an empty local slashing db. Best practice is to import (from a trusted source) that validator's attestation history. See [EIP 3076](https://github.com/ethereum/EIPs/pull/3076/files) for a standard slashing interchange format. From cd7b1056b4629d921fbb97ae7603e47d14eb5735 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 4 Nov 2020 09:02:57 -0600 Subject: [PATCH 47/48] add mainnet deposit contract address and min genesis time --- configs/mainnet/phase0.yaml | 6 +++--- specs/phase0/beacon-chain.md | 2 +- specs/phase0/deposit-contract.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/configs/mainnet/phase0.yaml b/configs/mainnet/phase0.yaml index 95eac0e40..ace44dd23 100644 --- a/configs/mainnet/phase0.yaml +++ b/configs/mainnet/phase0.yaml @@ -18,8 +18,8 @@ CHURN_LIMIT_QUOTIENT: 65536 SHUFFLE_ROUND_COUNT: 90 # `2**14` (= 16,384) MIN_GENESIS_ACTIVE_VALIDATOR_COUNT: 16384 -# Jan 3, 2020 -MIN_GENESIS_TIME: 1578009600 +# Dec 1, 2020, 12pm UTC +MIN_GENESIS_TIME: 1606824000 # 4 HYSTERESIS_QUOTIENT: 4 # 1 (minus 0.25) @@ -54,7 +54,7 @@ SECONDS_PER_ETH1_BLOCK: 14 DEPOSIT_CHAIN_ID: 1 DEPOSIT_NETWORK_ID: 1 # **TBD** -DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 +DEPOSIT_CONTRACT_ADDRESS: 0x00000000219ab540356cBB839Cbe05303d7705Fa # Gwei values diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 84e9af9dd..5f80d01b2 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -191,7 +191,7 @@ The following values are (non-configurable) constants used throughout the specif | `CHURN_LIMIT_QUOTIENT` | `uint64(2**16)` (= 65,536) | | `SHUFFLE_ROUND_COUNT` | `uint64(90)` | | `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT` | `uint64(2**14)` (= 16,384) | -| `MIN_GENESIS_TIME` | `uint64(1578009600)` (Jan 3, 2020) | +| `MIN_GENESIS_TIME` | `uint64(1606824000)` (Dec 1, 2020, 12pm UTC) | | `HYSTERESIS_QUOTIENT` | `uint64(4)` | | `HYSTERESIS_DOWNWARD_MULTIPLIER` | `uint64(1)` | | `HYSTERESIS_UPWARD_MULTIPLIER` | `uint64(5)` | diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 89c5bb22b..6036da2d6 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -43,7 +43,7 @@ These configurations are updated for releases and may be out of sync during `dev | - | - | | `DEPOSIT_CHAIN_ID` | `1` | | `DEPOSIT_NETWORK_ID` | `1` | -| `DEPOSIT_CONTRACT_ADDRESS` | **TBD** | +| `DEPOSIT_CONTRACT_ADDRESS` | `0x00000000219ab540356cBB839Cbe05303d7705Fa` | ## Ethereum 1.0 deposit contract From 7589af8e8d394a45647d2f2a7f271271e39addd1 Mon Sep 17 00:00:00 2001 From: Danny Ryan Date: Wed, 4 Nov 2020 09:18:46 -0600 Subject: [PATCH 48/48] remove wip notice on phase 0 specs --- configs/minimal/phase0.yaml | 2 +- specs/phase0/beacon-chain.md | 6 +----- specs/phase0/deposit-contract.md | 2 -- specs/phase0/fork-choice.md | 2 -- specs/phase0/p2p-interface.md | 2 -- specs/phase0/validator.md | 2 +- specs/phase0/weak-subjectivity.md | 2 -- ssz/simple-serialize.md | 2 -- .../eth2spec/test/phase0/genesis/test_validity.py | 10 ---------- 9 files changed, 3 insertions(+), 27 deletions(-) diff --git a/configs/minimal/phase0.yaml b/configs/minimal/phase0.yaml index aa9f1b126..93337adf8 100644 --- a/configs/minimal/phase0.yaml +++ b/configs/minimal/phase0.yaml @@ -54,7 +54,7 @@ SECONDS_PER_ETH1_BLOCK: 14 # Ethereum Goerli testnet DEPOSIT_CHAIN_ID: 5 DEPOSIT_NETWORK_ID: 5 -# **TBD** +# Configured on a per testnet basis DEPOSIT_CONTRACT_ADDRESS: 0x1234567890123456789012345678901234567890 diff --git a/specs/phase0/beacon-chain.md b/specs/phase0/beacon-chain.md index 5f80d01b2..fcd681115 100644 --- a/specs/phase0/beacon-chain.md +++ b/specs/phase0/beacon-chain.md @@ -1,7 +1,5 @@ # Ethereum 2.0 Phase 0 -- The Beacon Chain -**Notice**: This document is a work-in-progress for researchers and implementers. - ## Table of contents @@ -177,7 +175,7 @@ The following values are (non-configurable) constants used throughout the specif ## Configuration -*Note*: The default mainnet configuration values are included here for spec-design purposes. The different configurations for mainnet, testnets, and YAML-based testing can be found in the [`configs/constant_presets`](../../configs) directory. These configurations are updated for releases and may be out of sync during `dev` changes. +*Note*: The default mainnet configuration values are included here for illustrative purposes. The different configurations for mainnet, testnets, and YAML-based testing can be found in the [`configs/constant_presets`](../../configs) directory. ### Misc @@ -1191,8 +1189,6 @@ def is_valid_genesis_state(state: BeaconState) -> bool: return True ``` -*Note*: The `is_valid_genesis_state` function (including `MIN_GENESIS_TIME` and `MIN_GENESIS_ACTIVE_VALIDATOR_COUNT`) is a placeholder for testing. It has yet to be finalized by the community, and can be updated as necessary. - ### Genesis block Let `genesis_block = BeaconBlock(state_root=hash_tree_root(genesis_state))`. diff --git a/specs/phase0/deposit-contract.md b/specs/phase0/deposit-contract.md index 6036da2d6..8b3035629 100644 --- a/specs/phase0/deposit-contract.md +++ b/specs/phase0/deposit-contract.md @@ -1,7 +1,5 @@ # Ethereum 2.0 Phase 0 -- Deposit Contract -**Notice**: This document is a work-in-progress for researchers and implementers. - ## Table of contents diff --git a/specs/phase0/fork-choice.md b/specs/phase0/fork-choice.md index f0b25d95c..8bc108caa 100644 --- a/specs/phase0/fork-choice.md +++ b/specs/phase0/fork-choice.md @@ -1,7 +1,5 @@ # Ethereum 2.0 Phase 0 -- Beacon Chain Fork Choice -**Notice**: This document is a work-in-progress for researchers and implementers. - ## Table of contents diff --git a/specs/phase0/p2p-interface.md b/specs/phase0/p2p-interface.md index 0f1767710..43a097f7e 100644 --- a/specs/phase0/p2p-interface.md +++ b/specs/phase0/p2p-interface.md @@ -1219,8 +1219,6 @@ For minimum and maximum allowable slot broadcast times, Although messages can at times be eagerly gossiped to the network, the node's fork choice prevents integration of these messages into the actual consensus until the _actual local start_ of the designated slot. -The value of this constant is currently a placeholder and will be tuned based on data observed in testnets. - ### Why are there `ATTESTATION_SUBNET_COUNT` attestation subnets? Depending on the number of validators, it may be more efficient to group shard subnets and might provide better stability for the gossipsub channel. diff --git a/specs/phase0/validator.md b/specs/phase0/validator.md index 10cfc304a..c6a91dcfc 100644 --- a/specs/phase0/validator.md +++ b/specs/phase0/validator.md @@ -1,6 +1,6 @@ # Ethereum 2.0 Phase 0 -- Honest Validator -**Notice**: This document is a work-in-progress for researchers and implementers. This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. +This is an accompanying document to [Ethereum 2.0 Phase 0 -- The Beacon Chain](./beacon-chain.md), which describes the expected actions of a "validator" participating in the Ethereum 2.0 protocol. ## Table of contents diff --git a/specs/phase0/weak-subjectivity.md b/specs/phase0/weak-subjectivity.md index 7417053f5..9d433de8e 100644 --- a/specs/phase0/weak-subjectivity.md +++ b/specs/phase0/weak-subjectivity.md @@ -1,7 +1,5 @@ # Ethereum 2.0 Phase 0 -- Weak Subjectivity Guide -**Notice**: This document is a work-in-progress for researchers and implementers. - ## Table of contents diff --git a/ssz/simple-serialize.md b/ssz/simple-serialize.md index 4cc590ea2..b0e7ec1e0 100644 --- a/ssz/simple-serialize.md +++ b/ssz/simple-serialize.md @@ -1,7 +1,5 @@ # SimpleSerialize (SSZ) -**Notice**: This document is a work-in-progress describing typing, serialization, and Merkleization of Eth2 objects. - ## Table of contents diff --git a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py index 7bdfe1a42..28cd3544b 100644 --- a/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py +++ b/tests/core/pyspec/eth2spec/test/phase0/genesis/test_validity.py @@ -59,16 +59,6 @@ def test_is_valid_genesis_state_true_more_balance(spec): yield from run_is_valid_genesis_state(spec, state, valid=True) -# TODO: not part of the genesis function yet. Erroneously merged. -# @with_phases([PHASE0]) -# @spec_test -# def test_is_valid_genesis_state_false_not_enough_balance(spec): -# state = create_valid_beacon_state(spec) -# state.validators[0].effective_balance = spec.MAX_EFFECTIVE_BALANCE - 1 -# -# yield from run_is_valid_genesis_state(spec, state, valid=False) - - @with_phases([PHASE0]) @spec_test @single_phase