Legendre custody construction (#1305)
* Stab at Legendre custody construction + some tests * Fix some problems and fix function puller to remove phase0 only lines in phase1 * Pass the linter * Add headings * Fix domain for BLS stub * Change Jacobi to Legendre * n -> q to clarify notation * Headings * Another missing heading * Custody subchunks via padding * Fix max_reveal_lateness stuff * Better names for reveal period functions * Better parametrization of max_reveal_lateness computation and tests for custody reveal processing * Fix linter * Allow challenging for one period after the custody reveal, shorter periods for minimal tests * Fix lint * Fix linter error
This commit is contained in:
parent
13f6f18cd5
commit
b345dc0f5f
|
@ -74,6 +74,10 @@ MAX_EPOCHS_PER_CROSSLINK: 4
|
|||
MIN_EPOCHS_TO_INACTIVITY_PENALTY: 4
|
||||
# [customized] 2**12 (= 4,096) epochs
|
||||
EARLY_DERIVED_SECRET_PENALTY_MAX_FUTURE_EPOCHS: 4096
|
||||
# 2**2 (= 4) epochs
|
||||
EPOCHS_PER_CUSTODY_PERIOD: 4
|
||||
# 2**2 (= 4) epochs
|
||||
CUSTODY_PERIOD_TO_RANDAO_PADDING: 4
|
||||
|
||||
|
||||
# State vector lengths
|
||||
|
|
|
@ -58,10 +58,15 @@ from eth2spec.utils.bls import (
|
|||
bls_aggregate_pubkeys,
|
||||
bls_verify,
|
||||
bls_verify_multiple,
|
||||
bls_signature_to_G2,
|
||||
)
|
||||
|
||||
from eth2spec.utils.hash_function import hash
|
||||
'''
|
||||
SUNDRY_CONSTANTS_FUNCTIONS = '''
|
||||
def ceillog2(x: uint64) -> int:
|
||||
return (x - 1).bit_length()
|
||||
'''
|
||||
SUNDRY_FUNCTIONS = '''
|
||||
# Monkey patch hash cache
|
||||
_hash = hash
|
||||
|
@ -111,6 +116,13 @@ def apply_constants_preset(preset: Dict[str, Any]) -> None:
|
|||
'''
|
||||
|
||||
|
||||
def remove_for_phase1(functions: Dict[str, str]):
|
||||
for key, value in functions.items():
|
||||
lines = value.split("\n")
|
||||
lines = filter(lambda s: "[to be removed in phase 1]" not in s, lines)
|
||||
functions[key] = "\n".join(lines)
|
||||
|
||||
|
||||
def strip_comments(raw: str) -> str:
|
||||
comment_line_regex = re.compile(r'^\s+# ')
|
||||
lines = raw.split('\n')
|
||||
|
@ -141,10 +153,15 @@ def objects_to_spec(functions: Dict[str, str],
|
|||
]
|
||||
)
|
||||
)
|
||||
for k in list(functions):
|
||||
if "ceillog2" in k:
|
||||
del functions[k]
|
||||
functions_spec = '\n\n'.join(functions.values())
|
||||
for k in list(constants.keys()):
|
||||
if k.startswith('DOMAIN_'):
|
||||
constants[k] = f"DomainType(({constants[k]}).to_bytes(length=4, byteorder='little'))"
|
||||
if k == "BLS12_381_Q":
|
||||
constants[k] += " # noqa: E501"
|
||||
constants_spec = '\n'.join(map(lambda x: '%s = %s' % (x, constants[x]), constants))
|
||||
ssz_objects_instantiation_spec = '\n\n'.join(ssz_objects.values())
|
||||
ssz_objects_reinitialization_spec = (
|
||||
|
@ -157,6 +174,7 @@ def objects_to_spec(functions: Dict[str, str],
|
|||
spec = (
|
||||
imports
|
||||
+ '\n\n' + new_type_definitions
|
||||
+ '\n' + SUNDRY_CONSTANTS_FUNCTIONS
|
||||
+ '\n\n' + constants_spec
|
||||
+ '\n\n\n' + ssz_objects_instantiation_spec
|
||||
+ '\n\n' + functions_spec
|
||||
|
@ -186,7 +204,7 @@ ignored_dependencies = [
|
|||
'bit', 'boolean', 'Vector', 'List', 'Container', 'Hash', 'BLSPubkey', 'BLSSignature', 'Bytes', 'BytesN'
|
||||
'Bytes1', 'Bytes4', 'Bytes32', 'Bytes48', 'Bytes96', 'Bitlist', 'Bitvector',
|
||||
'uint8', 'uint16', 'uint32', 'uint64', 'uint128', 'uint256',
|
||||
'bytes' # to be removed after updating spec doc
|
||||
'bytes', 'byte', 'BytesN' # to be removed after updating spec doc
|
||||
]
|
||||
|
||||
|
||||
|
@ -268,6 +286,7 @@ def build_phase1_spec(phase0_sourcefile: str,
|
|||
fork_choice_sourcefile: str,
|
||||
outfile: str=None) -> Optional[str]:
|
||||
phase0_spec = get_spec(phase0_sourcefile)
|
||||
remove_for_phase1(phase0_spec[0])
|
||||
phase1_custody = get_spec(phase1_custody_sourcefile)
|
||||
phase1_shard_data = get_spec(phase1_shard_sourcefile)
|
||||
fork_choice_spec = get_spec(fork_choice_sourcefile)
|
||||
|
|
|
@ -660,8 +660,8 @@ def is_valid_indexed_attestation(state: BeaconState, indexed_attestation: Indexe
|
|||
bit_1_indices = indexed_attestation.custody_bit_1_indices
|
||||
|
||||
# Verify no index has custody bit equal to 1 [to be removed in phase 1]
|
||||
if not len(bit_1_indices) == 0:
|
||||
return False
|
||||
if not len(bit_1_indices) == 0: # [to be removed in phase 1]
|
||||
return False # [to be removed in phase 1]
|
||||
# Verify max number of indices
|
||||
if not len(bit_0_indices) + len(bit_1_indices) <= MAX_VALIDATORS_PER_COMMITTEE:
|
||||
return False
|
||||
|
@ -1661,6 +1661,11 @@ def process_attestation(state: BeaconState, attestation: Attestation) -> None:
|
|||
proposer_index=get_beacon_proposer_index(state),
|
||||
)
|
||||
|
||||
# Check bitlist lengths
|
||||
committee_size = get_committee_count(state, attestation.data.target.epoch)
|
||||
assert len(attestation.aggregation_bits) == committee_size
|
||||
assert len(attestation.custody_bits) == committee_size
|
||||
|
||||
if data.target.epoch == get_current_epoch(state):
|
||||
assert data.source == state.current_justified_checkpoint
|
||||
parent_crosslink = state.current_crosslinks[data.crosslink.shard]
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
- [Terminology](#terminology)
|
||||
- [Constants](#constants)
|
||||
- [Misc](#misc)
|
||||
- [Custody game parameters](#custody-game-parameters)
|
||||
- [Time parameters](#time-parameters)
|
||||
- [Max operations per block](#max-operations-per-block)
|
||||
- [Reward and penalty quotients](#reward-and-penalty-quotients)
|
||||
|
@ -33,12 +34,14 @@
|
|||
- [`BeaconBlockBody`](#beaconblockbody)
|
||||
- [Helpers](#helpers)
|
||||
- [`ceillog2`](#ceillog2)
|
||||
- [`is_valid_merkle_branch_with_mixin`](#is_valid_merkle_branch_with_mixin)
|
||||
- [`get_crosslink_chunk_count`](#get_crosslink_chunk_count)
|
||||
- [`get_bit`](#get_bit)
|
||||
- [`legendre_bit`](#legendre_bit)
|
||||
- [`custody_subchunkify`](#custody_subchunkify)
|
||||
- [`get_custody_chunk_bit`](#get_custody_chunk_bit)
|
||||
- [`get_chunk_bits_root`](#get_chunk_bits_root)
|
||||
- [`get_randao_epoch_for_custody_period`](#get_randao_epoch_for_custody_period)
|
||||
- [`get_reveal_period`](#get_reveal_period)
|
||||
- [`get_custody_period_for_validator`](#get_custody_period_for_validator)
|
||||
- [`replace_empty_or_append`](#replace_empty_or_append)
|
||||
- [Per-block processing](#per-block-processing)
|
||||
- [Operations](#operations)
|
||||
|
@ -75,11 +78,20 @@ This document details the beacon chain additions and changes in Phase 1 of Ether
|
|||
|
||||
### Misc
|
||||
|
||||
| `BLS12_381_Q` | `4002409555221667393417789825735904156556882819939007885332058136124031650490837864442687629129015664037894272559787` |
|
||||
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) |
|
||||
|
||||
### Custody game parameters
|
||||
|
||||
| Name | Value |
|
||||
| - | - |
|
||||
| `BYTES_PER_SHARD_BLOCK` | `2**14` (= 16,384) |
|
||||
| `BYTES_PER_CUSTODY_CHUNK` | `2**9` (= 512) |
|
||||
| `MINOR_REWARD_QUOTIENT` | `2**8` (= 256) |
|
||||
| `BYTES_PER_CUSTODY_SUBCHUNK` | `48` |
|
||||
| `CHUNKS_PER_EPOCH` | `2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK` |
|
||||
| `MAX_CUSTODY_CHUNKS` | `MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH` |
|
||||
| `CUSTODY_DATA_DEPTH` | `ceillog2(MAX_CUSTODY_CHUNKS) + 1` |
|
||||
| `CUSTODY_CHUNK_BIT_DEPTH` | `ceillog2(MAX_EPOCHS_PER_CROSSLINK * CHUNKS_PER_EPOCH // 256) + 2` |
|
||||
|
||||
### Time parameters
|
||||
|
||||
|
@ -144,7 +156,7 @@ class CustodyBitChallenge(Container):
|
|||
attestation: Attestation
|
||||
challenger_index: ValidatorIndex
|
||||
responder_key: BLSSignature
|
||||
chunk_bits: Bytes[PLACEHOLDER]
|
||||
chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS]
|
||||
signature: BLSSignature
|
||||
```
|
||||
|
||||
|
@ -181,10 +193,10 @@ class CustodyBitChallengeRecord(Container):
|
|||
class CustodyResponse(Container):
|
||||
challenge_index: uint64
|
||||
chunk_index: uint64
|
||||
chunk: Vector[Bytes[PLACEHOLDER], BYTES_PER_CUSTODY_CHUNK]
|
||||
data_branch: List[Hash, PLACEHOLDER]
|
||||
chunk_bits_branch: List[Hash, PLACEHOLDER]
|
||||
chunk_bits_leaf: Hash
|
||||
chunk: BytesN[BYTES_PER_CUSTODY_CHUNK]
|
||||
data_branch: List[Hash, CUSTODY_DATA_DEPTH]
|
||||
chunk_bits_branch: List[Hash, CUSTODY_CHUNK_BIT_DEPTH]
|
||||
chunk_bits_leaf: Bitvector[256]
|
||||
```
|
||||
|
||||
### New beacon operations
|
||||
|
@ -225,11 +237,11 @@ Add the following fields to the end of the specified container objects. Fields w
|
|||
|
||||
```python
|
||||
class Validator(Container):
|
||||
# next_custody_reveal_period is initialised to the custody period
|
||||
# next_custody_secret_to_reveal is initialised to the custody period
|
||||
# (of the particular validator) in which the validator is activated
|
||||
# = get_reveal_period(...)
|
||||
next_custody_reveal_period: uint64
|
||||
max_reveal_lateness: uint64
|
||||
# = get_custody_period_for_validator(...)
|
||||
next_custody_secret_to_reveal: uint64
|
||||
max_reveal_lateness: Epoch
|
||||
```
|
||||
|
||||
#### `BeaconState`
|
||||
|
@ -263,7 +275,26 @@ class BeaconBlockBody(Container):
|
|||
|
||||
```python
|
||||
def ceillog2(x: uint64) -> int:
|
||||
return x.bit_length()
|
||||
return (x - 1).bit_length()
|
||||
```
|
||||
|
||||
### `is_valid_merkle_branch_with_mixin`
|
||||
|
||||
```python
|
||||
def is_valid_merkle_branch_with_mixin(leaf: Hash,
|
||||
branch: Sequence[Hash],
|
||||
depth: uint64,
|
||||
index: uint64,
|
||||
root: Hash,
|
||||
mixin: uint64) -> bool:
|
||||
value = leaf
|
||||
for i in range(depth):
|
||||
if index // (2**i) % 2:
|
||||
value = hash(branch[i] + value)
|
||||
else:
|
||||
value = hash(value + branch[i])
|
||||
value = hash(value + mixin.to_bytes(32, "little"))
|
||||
return value == root
|
||||
```
|
||||
|
||||
### `get_crosslink_chunk_count`
|
||||
|
@ -271,37 +302,69 @@ def ceillog2(x: uint64) -> int:
|
|||
```python
|
||||
def get_custody_chunk_count(crosslink: Crosslink) -> int:
|
||||
crosslink_length = min(MAX_EPOCHS_PER_CROSSLINK, crosslink.end_epoch - crosslink.start_epoch)
|
||||
chunks_per_epoch = 2 * BYTES_PER_SHARD_BLOCK * SLOTS_PER_EPOCH // BYTES_PER_CUSTODY_CHUNK
|
||||
return crosslink_length * chunks_per_epoch
|
||||
return crosslink_length * CHUNKS_PER_EPOCH
|
||||
```
|
||||
|
||||
### `get_bit`
|
||||
### `legendre_bit`
|
||||
|
||||
Returns the Legendre symbol `(a/q)` normalizes as a bit (i.e. `((a/q) + 1) // 2`). In a production implementation, a well-optimized library (e.g. GMP) should be used for this.
|
||||
|
||||
```python
|
||||
def get_bit(serialization: bytes, i: uint64) -> int:
|
||||
"""
|
||||
Extract the bit in ``serialization`` at position ``i``.
|
||||
"""
|
||||
return (serialization[i // 8] >> (i % 8)) % 2
|
||||
def legendre_bit(a: int, q: int) -> int:
|
||||
if a >= q:
|
||||
return legendre_bit(a % q, q)
|
||||
if a == 0:
|
||||
return 0
|
||||
assert(q > a > 0 and q % 2 == 1)
|
||||
t = 1
|
||||
n = q
|
||||
while a != 0:
|
||||
while a % 2 == 0:
|
||||
a //= 2
|
||||
r = n % 8
|
||||
if r == 3 or r == 5:
|
||||
t = -t
|
||||
a, n = n, a
|
||||
if a % 4 == n % 4 == 3:
|
||||
t = -t
|
||||
a %= n
|
||||
if n == 1:
|
||||
return (t + 1) // 2
|
||||
else:
|
||||
return 0
|
||||
```
|
||||
|
||||
### ```custody_subchunkify```
|
||||
|
||||
Given one proof of custody chunk, returns the proof of custody subchunks of the correct sizes.
|
||||
|
||||
```python
|
||||
def custody_subchunkify(bytez: bytes) -> list:
|
||||
bytez += b'\x00' * (-len(bytez) % BYTES_PER_CUSTODY_SUBCHUNK)
|
||||
return [bytez[i:i + BYTES_PER_CUSTODY_SUBCHUNK]
|
||||
for i in range(0, len(bytez), BYTES_PER_CUSTODY_SUBCHUNK)]
|
||||
```
|
||||
|
||||
### `get_custody_chunk_bit`
|
||||
|
||||
```python
|
||||
def get_custody_chunk_bit(key: BLSSignature, chunk: bytes) -> bool:
|
||||
# TODO: Replace with something MPC-friendly, e.g. the Legendre symbol
|
||||
return bool(get_bit(hash(key + chunk), 0))
|
||||
full_G2_element = bls_signature_to_G2(key)
|
||||
s = full_G2_element[0].coeffs
|
||||
bits = [legendre_bit((i + 1) * s[i % 2] + int.from_bytes(subchunk, "little"), BLS12_381_Q)
|
||||
for i, subchunk in enumerate(custody_subchunkify(chunk))]
|
||||
|
||||
return bool(sum(bits) % 2)
|
||||
```
|
||||
|
||||
### `get_chunk_bits_root`
|
||||
|
||||
```python
|
||||
def get_chunk_bits_root(chunk_bits: bytes) -> Hash:
|
||||
aggregated_bits = bytearray([0] * 32)
|
||||
for i in range(0, len(chunk_bits), 32):
|
||||
for j in range(32):
|
||||
aggregated_bits[j] ^= chunk_bits[i + j]
|
||||
return hash(aggregated_bits)
|
||||
def get_chunk_bits_root(chunk_bits: Bitlist[MAX_CUSTODY_CHUNKS]) -> bit:
|
||||
aggregated_bits = 0
|
||||
for i, b in enumerate(chunk_bits):
|
||||
aggregated_bits += 2**i * b
|
||||
return legendre_bit(aggregated_bits, BLS12_381_Q)
|
||||
```
|
||||
|
||||
### `get_randao_epoch_for_custody_period`
|
||||
|
@ -312,10 +375,10 @@ def get_randao_epoch_for_custody_period(period: uint64, validator_index: Validat
|
|||
return Epoch(next_period_start + CUSTODY_PERIOD_TO_RANDAO_PADDING)
|
||||
```
|
||||
|
||||
### `get_reveal_period`
|
||||
### `get_custody_period_for_validator`
|
||||
|
||||
```python
|
||||
def get_reveal_period(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int:
|
||||
def get_custody_period_for_validator(state: BeaconState, validator_index: ValidatorIndex, epoch: Epoch=None) -> int:
|
||||
'''
|
||||
Return the reveal period for a given validator.
|
||||
'''
|
||||
|
@ -354,9 +417,9 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
|
|||
Note that this function mutates ``state``.
|
||||
"""
|
||||
revealer = state.validators[reveal.revealer_index]
|
||||
epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_reveal_period, reveal.revealed_index)
|
||||
epoch_to_sign = get_randao_epoch_for_custody_period(revealer.next_custody_secret_to_reveal, reveal.revealer_index)
|
||||
|
||||
assert revealer.next_custody_reveal_period < get_reveal_period(state, reveal.revealed_index)
|
||||
assert revealer.next_custody_secret_to_reveal < get_custody_period_for_validator(state, reveal.revealer_index)
|
||||
|
||||
# Revealed validator is active or exited, but not withdrawn
|
||||
assert is_slashable_validator(revealer, get_current_epoch(state))
|
||||
|
@ -374,15 +437,19 @@ def process_custody_key_reveal(state: BeaconState, reveal: CustodyKeyReveal) ->
|
|||
)
|
||||
|
||||
# Decrement max reveal lateness if response is timely
|
||||
if revealer.next_custody_reveal_period == get_reveal_period(state, reveal.revealer_index) - 2:
|
||||
if epoch_to_sign + EPOCHS_PER_CUSTODY_PERIOD >= get_current_epoch(state):
|
||||
if revealer.max_reveal_lateness >= MAX_REVEAL_LATENESS_DECREMENT:
|
||||
revealer.max_reveal_lateness -= MAX_REVEAL_LATENESS_DECREMENT
|
||||
else:
|
||||
revealer.max_reveal_lateness = 0
|
||||
else:
|
||||
revealer.max_reveal_lateness = max(
|
||||
revealer.max_reveal_lateness,
|
||||
get_reveal_period(state, reveal.revealed_index) - revealer.next_custody_reveal_period
|
||||
get_current_epoch(state) - epoch_to_sign - EPOCHS_PER_CUSTODY_PERIOD
|
||||
)
|
||||
|
||||
# Process reveal
|
||||
revealer.next_custody_reveal_period += 1
|
||||
revealer.next_custody_secret_to_reveal += 1
|
||||
|
||||
# Reward Block Preposer
|
||||
proposer_index = get_beacon_proposer_index(state)
|
||||
|
@ -520,7 +587,7 @@ For each `challenge` in `block.body.custody_bit_challenges`, run the following f
|
|||
```python
|
||||
def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) -> None:
|
||||
attestation = challenge.attestation
|
||||
epoch = compute_epoch_of_slot(attestation.data.slot)
|
||||
epoch = attestation.data.target.epoch
|
||||
shard = attestation.data.crosslink.shard
|
||||
|
||||
# Verify challenge signature
|
||||
|
@ -533,7 +600,10 @@ def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) ->
|
|||
assert is_valid_indexed_attestation(state, get_indexed_attestation(state, attestation))
|
||||
# Verify attestation is eligible for challenging
|
||||
responder = state.validators[challenge.responder_index]
|
||||
assert epoch + responder.max_reveal_lateness <= get_reveal_period(state, challenge.responder_index)
|
||||
assert get_current_epoch(state) <= get_randao_epoch_for_custody_period(
|
||||
get_custody_period_for_validator(state, challenge.responder_index, epoch),
|
||||
challenge.responder_index
|
||||
) + 2 * EPOCHS_PER_CUSTODY_PERIOD + responder.max_reveal_lateness
|
||||
|
||||
# Verify the responder participated in the attestation
|
||||
attesters = get_attesting_indices(state, attestation.data, attestation.aggregation_bits)
|
||||
|
@ -543,17 +613,18 @@ def process_bit_challenge(state: BeaconState, challenge: CustodyBitChallenge) ->
|
|||
assert record.challenger_index != challenge.challenger_index
|
||||
# Verify the responder custody key
|
||||
epoch_to_sign = get_randao_epoch_for_custody_period(
|
||||
get_reveal_period(state, challenge.responder_index, epoch),
|
||||
get_custody_period_for_validator(state, challenge.responder_index, epoch),
|
||||
challenge.responder_index,
|
||||
)
|
||||
domain = get_domain(state, DOMAIN_RANDAO, epoch_to_sign)
|
||||
assert bls_verify(responder.pubkey, hash_tree_root(epoch_to_sign), challenge.responder_key, domain)
|
||||
# Verify the chunk count
|
||||
chunk_count = get_custody_chunk_count(attestation.data.crosslink)
|
||||
# Verify the first bit of the hash of the chunk bits does not equal the custody bit
|
||||
assert chunk_count == len(challenge.chunk_bits)
|
||||
# Verify custody bit is incorrect
|
||||
committee = get_crosslink_committee(state, epoch, shard)
|
||||
custody_bit = attestation.custody_bits[committee.index(challenge.responder_index)]
|
||||
assert custody_bit != get_bit(get_chunk_bits_root(challenge.chunk_bits), 0)
|
||||
assert custody_bit != get_chunk_bits_root(challenge.chunk_bits)
|
||||
# Add new bit challenge record
|
||||
new_record = CustodyBitChallengeRecord(
|
||||
challenge_index=state.custody_challenge_index,
|
||||
|
@ -636,16 +707,17 @@ def process_bit_challenge_response(state: BeaconState,
|
|||
root=challenge.data_root,
|
||||
)
|
||||
# Verify the chunk bit leaf matches the challenge data
|
||||
assert is_valid_merkle_branch(
|
||||
leaf=response.chunk_bits_leaf,
|
||||
assert is_valid_merkle_branch_with_mixin(
|
||||
leaf=hash_tree_root(response.chunk_bits_leaf),
|
||||
branch=response.chunk_bits_branch,
|
||||
depth=ceillog2(challenge.chunk_count) >> 8,
|
||||
depth=ceillog2(MAX_CUSTODY_CHUNKS // 256),
|
||||
index=response.chunk_index // 256,
|
||||
root=challenge.chunk_bits_merkle_root
|
||||
root=challenge.chunk_bits_merkle_root,
|
||||
mixin=challenge.chunk_count,
|
||||
)
|
||||
# Verify the chunk bit does not match the challenge chunk bit
|
||||
assert (get_custody_chunk_bit(challenge.responder_key, response.chunk)
|
||||
!= get_bit(challenge.chunk_bits_leaf, response.chunk_index % 256))
|
||||
!= response.chunk_bits_leaf[response.chunk_index % 256])
|
||||
# Clear the challenge
|
||||
records = state.custody_bit_challenge_records
|
||||
records[records.index(challenge)] = CustodyBitChallengeRecord()
|
||||
|
@ -665,8 +737,8 @@ Run `process_reveal_deadlines(state)` immediately after `process_registry_update
|
|||
# end insert @process_reveal_deadlines
|
||||
def process_reveal_deadlines(state: BeaconState) -> None:
|
||||
for index, validator in enumerate(state.validators):
|
||||
deadline = validator.next_custody_reveal_period + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD)
|
||||
if get_reveal_period(state, ValidatorIndex(index)) > deadline:
|
||||
deadline = validator.next_custody_secret_to_reveal + (CUSTODY_RESPONSE_DEADLINE // EPOCHS_PER_CUSTODY_PERIOD)
|
||||
if get_custody_period_for_validator(state, ValidatorIndex(index)) > deadline:
|
||||
slash_validator(state, ValidatorIndex(index))
|
||||
```
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from eth2spec.test.context import with_all_phases, with_state, bls_switch
|
||||
from eth2spec.test.context import with_all_phases, with_state, bls_switch, with_phases
|
||||
|
||||
from eth2spec.test.helpers.block import build_empty_block_for_next_slot
|
||||
from eth2spec.test.helpers.attestations import get_valid_attestation
|
||||
|
@ -103,7 +103,7 @@ def test_on_attestation_same_slot(spec, state):
|
|||
run_on_attestation(spec, state, store, attestation, False)
|
||||
|
||||
|
||||
@with_all_phases
|
||||
@with_phases(['phase0'])
|
||||
@with_state
|
||||
@bls_switch
|
||||
def test_on_attestation_invalid_attestation(spec, state):
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
from eth2spec.test.helpers.keys import privkeys
|
||||
from eth2spec.utils.bls import bls_sign, bls_aggregate_signatures
|
||||
from eth2spec.utils.hash_function import hash
|
||||
from eth2spec.utils.ssz.ssz_typing import Bitlist, BytesN, Bitvector
|
||||
from eth2spec.utils.ssz.ssz_impl import chunkify, pack, hash_tree_root
|
||||
from eth2spec.utils.merkle_minimal import get_merkle_tree, get_merkle_proof
|
||||
|
||||
BYTES_PER_CHUNK = 32
|
||||
|
||||
|
||||
def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
|
||||
|
@ -13,7 +18,7 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
|
|||
|
||||
# Generate the secret that is being revealed
|
||||
reveal = bls_sign(
|
||||
message_hash=spec.hash_tree_root(spec.Epoch(epoch)),
|
||||
message_hash=hash_tree_root(spec.Epoch(epoch)),
|
||||
privkey=privkeys[revealed_index],
|
||||
domain=spec.get_domain(
|
||||
state=state,
|
||||
|
@ -42,3 +47,128 @@ def get_valid_early_derived_secret_reveal(spec, state, epoch=None):
|
|||
masker_index=masker_index,
|
||||
mask=mask,
|
||||
)
|
||||
|
||||
|
||||
def get_valid_custody_key_reveal(spec, state, period=None):
|
||||
current_epoch = spec.get_current_epoch(state)
|
||||
revealer_index = spec.get_active_validator_indices(state, current_epoch)[0]
|
||||
revealer = state.validators[revealer_index]
|
||||
|
||||
if period is None:
|
||||
period = revealer.next_custody_secret_to_reveal
|
||||
|
||||
epoch_to_sign = spec.get_randao_epoch_for_custody_period(period, revealer_index)
|
||||
|
||||
# Generate the secret that is being revealed
|
||||
reveal = bls_sign(
|
||||
message_hash=hash_tree_root(spec.Epoch(epoch_to_sign)),
|
||||
privkey=privkeys[revealer_index],
|
||||
domain=spec.get_domain(
|
||||
state=state,
|
||||
domain_type=spec.DOMAIN_RANDAO,
|
||||
message_epoch=epoch_to_sign,
|
||||
),
|
||||
)
|
||||
return spec.CustodyKeyReveal(
|
||||
revealer_index=revealer_index,
|
||||
reveal=reveal,
|
||||
)
|
||||
|
||||
|
||||
def bitlist_from_int(max_len, num_bits, n):
|
||||
return Bitlist[max_len](*[(n >> i) & 0b1 for i in range(num_bits)])
|
||||
|
||||
|
||||
def get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=False):
|
||||
crosslink_committee = spec.get_crosslink_committee(
|
||||
state,
|
||||
attestation.data.target.epoch,
|
||||
attestation.data.crosslink.shard,
|
||||
)
|
||||
responder_index = crosslink_committee[0]
|
||||
challenger_index = crosslink_committee[-1]
|
||||
|
||||
epoch = spec.get_randao_epoch_for_custody_period(attestation.data.target.epoch,
|
||||
responder_index)
|
||||
|
||||
# Generate the responder key
|
||||
responder_key = bls_sign(
|
||||
message_hash=hash_tree_root(spec.Epoch(epoch)),
|
||||
privkey=privkeys[responder_index],
|
||||
domain=spec.get_domain(
|
||||
state=state,
|
||||
domain_type=spec.DOMAIN_RANDAO,
|
||||
message_epoch=epoch,
|
||||
),
|
||||
)
|
||||
|
||||
chunk_count = spec.get_custody_chunk_count(attestation.data.crosslink)
|
||||
|
||||
chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, 0)
|
||||
|
||||
n = 0
|
||||
while spec.get_chunk_bits_root(chunk_bits) == attestation.custody_bits[0] ^ invalid_custody_bit:
|
||||
chunk_bits = bitlist_from_int(spec.MAX_CUSTODY_CHUNKS, chunk_count, n)
|
||||
n += 1
|
||||
|
||||
return spec.CustodyBitChallenge(
|
||||
responder_index=responder_index,
|
||||
attestation=attestation,
|
||||
challenger_index=challenger_index,
|
||||
responder_key=responder_key,
|
||||
chunk_bits=chunk_bits,
|
||||
)
|
||||
|
||||
|
||||
def custody_chunkify(spec, x):
|
||||
chunks = [bytes(x[i:i + spec.BYTES_PER_CUSTODY_CHUNK]) for i in range(0, len(x), spec.BYTES_PER_CUSTODY_CHUNK)]
|
||||
chunks[-1] = chunks[-1].ljust(spec.BYTES_PER_CUSTODY_CHUNK, b"\0")
|
||||
return chunks
|
||||
|
||||
|
||||
def get_valid_custody_response(spec, state, bit_challenge, custody_data, challenge_index, invalid_chunk_bit=False):
|
||||
chunks = custody_chunkify(spec, custody_data)
|
||||
|
||||
chunk_index = len(chunks) - 1
|
||||
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
|
||||
|
||||
while chunk_bit == bit_challenge.chunk_bits[chunk_index] ^ invalid_chunk_bit:
|
||||
chunk_index -= 1
|
||||
chunk_bit = spec.get_custody_chunk_bit(bit_challenge.responder_key, chunks[chunk_index])
|
||||
|
||||
chunks_hash_tree_roots = [hash_tree_root(BytesN[spec.BYTES_PER_CUSTODY_CHUNK](chunk)) for chunk in chunks]
|
||||
chunks_hash_tree_roots += [
|
||||
hash_tree_root(BytesN[spec.BYTES_PER_CUSTODY_CHUNK](b"\0" * spec.BYTES_PER_CUSTODY_CHUNK))
|
||||
for i in range(2 ** spec.ceillog2(len(chunks)) - len(chunks))]
|
||||
data_tree = get_merkle_tree(chunks_hash_tree_roots)
|
||||
|
||||
data_branch = get_merkle_proof(data_tree, chunk_index)
|
||||
|
||||
bitlist_chunk_index = chunk_index // BYTES_PER_CHUNK
|
||||
bitlist_chunks = chunkify(pack(bit_challenge.chunk_bits))
|
||||
bitlist_tree = get_merkle_tree(bitlist_chunks, pad_to=spec.MAX_CUSTODY_CHUNKS // 256)
|
||||
bitlist_chunk_branch = get_merkle_proof(bitlist_tree, chunk_index // 256) + \
|
||||
[len(bit_challenge.chunk_bits).to_bytes(32, "little")]
|
||||
|
||||
bitlist_chunk_index = chunk_index // 256
|
||||
|
||||
chunk_bits_leaf = Bitvector[256](bit_challenge.chunk_bits[bitlist_chunk_index * 256:
|
||||
(bitlist_chunk_index + 1) * 256])
|
||||
|
||||
return spec.CustodyResponse(
|
||||
challenge_index=challenge_index,
|
||||
chunk_index=chunk_index,
|
||||
chunk=BytesN[spec.BYTES_PER_CUSTODY_CHUNK](chunks[chunk_index]),
|
||||
data_branch=data_branch,
|
||||
chunk_bits_branch=bitlist_chunk_branch,
|
||||
chunk_bits_leaf=chunk_bits_leaf,
|
||||
)
|
||||
|
||||
|
||||
def get_custody_test_vector(bytelength):
|
||||
ints = bytelength // 4
|
||||
return b"".join(i.to_bytes(4, "little") for i in range(ints))
|
||||
|
||||
|
||||
def get_custody_merkle_root(data):
|
||||
return get_merkle_tree(chunkify(data))[-1][0]
|
||||
|
|
|
@ -47,7 +47,7 @@ def build_deposit(spec,
|
|||
deposit_data_list.append(deposit_data)
|
||||
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)) + [(index + 1).to_bytes(32, 'little')]
|
||||
proof = list(get_merkle_proof(tree, item_index=index, tree_len=32)) + [(index + 1).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)
|
||||
|
|
|
@ -363,7 +363,7 @@ def test_inconsistent_bits(spec, state):
|
|||
attestation = get_valid_attestation(spec, state)
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
custody_bits = attestation.aggregation_bits[:]
|
||||
custody_bits = attestation.custody_bits[:]
|
||||
custody_bits.append(False)
|
||||
|
||||
attestation.custody_bits = custody_bits
|
||||
|
|
|
@ -0,0 +1,347 @@
|
|||
from eth2spec.test.helpers.custody import (
|
||||
get_valid_bit_challenge,
|
||||
get_valid_custody_response,
|
||||
get_custody_test_vector,
|
||||
get_custody_merkle_root
|
||||
)
|
||||
from eth2spec.test.helpers.attestations import (
|
||||
get_valid_attestation,
|
||||
)
|
||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||
from eth2spec.test.helpers.state import next_epoch, get_balance
|
||||
from eth2spec.test.helpers.block import apply_empty_block
|
||||
from eth2spec.test.context import (
|
||||
with_all_phases_except,
|
||||
spec_state_test,
|
||||
expect_assertion_error,
|
||||
)
|
||||
from eth2spec.test.phase_0.block_processing.test_process_attestation import run_attestation_processing
|
||||
|
||||
|
||||
def run_bit_challenge_processing(spec, state, custody_bit_challenge, valid=True):
|
||||
"""
|
||||
Run ``process_bit_challenge``, yielding:
|
||||
- pre-state ('pre')
|
||||
- CustodyBitChallenge ('custody_bit_challenge')
|
||||
- post-state ('post').
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
yield 'pre', state
|
||||
yield 'custody_bit_challenge', custody_bit_challenge
|
||||
|
||||
if not valid:
|
||||
expect_assertion_error(lambda: spec.process_bit_challenge(state, custody_bit_challenge))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
spec.process_bit_challenge(state, custody_bit_challenge)
|
||||
|
||||
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].chunk_bits_merkle_root == \
|
||||
hash_tree_root(custody_bit_challenge.chunk_bits)
|
||||
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].challenger_index == \
|
||||
custody_bit_challenge.challenger_index
|
||||
assert state.custody_bit_challenge_records[state.custody_challenge_index - 1].responder_index == \
|
||||
custody_bit_challenge.responder_index
|
||||
|
||||
yield 'post', state
|
||||
|
||||
|
||||
def run_custody_response_processing(spec, state, custody_response, valid=True):
|
||||
"""
|
||||
Run ``process_bit_challenge_response``, yielding:
|
||||
- pre-state ('pre')
|
||||
- CustodyResponse ('custody_response')
|
||||
- post-state ('post').
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
yield 'pre', state
|
||||
yield 'custody_response', custody_response
|
||||
|
||||
if not valid:
|
||||
expect_assertion_error(lambda: spec.process_custody_response(state, custody_response))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
# TODO: Add capability to also process chunk challenges, not only bit challenges
|
||||
challenge = state.custody_bit_challenge_records[custody_response.challenge_index]
|
||||
pre_slashed_balance = get_balance(state, challenge.challenger_index)
|
||||
|
||||
spec.process_custody_response(state, custody_response)
|
||||
|
||||
slashed_validator = state.validators[challenge.challenger_index]
|
||||
|
||||
assert slashed_validator.slashed
|
||||
assert slashed_validator.exit_epoch < spec.FAR_FUTURE_EPOCH
|
||||
assert slashed_validator.withdrawable_epoch < spec.FAR_FUTURE_EPOCH
|
||||
|
||||
assert get_balance(state, challenge.challenger_index) < pre_slashed_balance
|
||||
yield 'post', state
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_challenge_appended(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_multiple_epochs_custody(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH * 3
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_many_epochs_custody(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH * 100
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
state.slot += spec.SLOTS_PER_EPOCH * (spec.EPOCHS_PER_CUSTODY_PERIOD - 1)
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_off_chain_attestation(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_invalid_custody_bit_challenge(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation, invalid_custody_bit=True)
|
||||
|
||||
yield from run_bit_challenge_processing(spec, state, challenge, valid=False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_max_reveal_lateness_1(spec, state):
|
||||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
responder_index = challenge.responder_index
|
||||
|
||||
state.validators[responder_index].max_reveal_lateness = 3
|
||||
|
||||
for i in range(spec.get_randao_epoch_for_custody_period(
|
||||
spec.get_custody_period_for_validator(state, responder_index),
|
||||
responder_index
|
||||
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 2):
|
||||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
yield from run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_max_reveal_lateness_2(spec, state):
|
||||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
responder_index = challenge.responder_index
|
||||
|
||||
state.validators[responder_index].max_reveal_lateness = 3
|
||||
|
||||
for i in range(spec.get_randao_epoch_for_custody_period(
|
||||
spec.get_custody_period_for_validator(state, responder_index),
|
||||
responder_index
|
||||
) + 2 * spec.EPOCHS_PER_CUSTODY_PERIOD + state.validators[responder_index].max_reveal_lateness - 1):
|
||||
next_epoch(spec, state)
|
||||
apply_empty_block(spec, state)
|
||||
|
||||
yield from run_bit_challenge_processing(spec, state, challenge, False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_custody_response(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
bit_challenge_index = state.custody_challenge_index - 1
|
||||
|
||||
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
|
||||
|
||||
yield from run_custody_response_processing(spec, state, custody_response)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_custody_response_multiple_epochs(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH * 3
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
bit_challenge_index = state.custody_challenge_index - 1
|
||||
|
||||
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
|
||||
|
||||
yield from run_custody_response_processing(spec, state, custody_response)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@spec_state_test
|
||||
def test_custody_response_many_epochs(spec, state):
|
||||
state.slot = spec.SLOTS_PER_EPOCH * 100
|
||||
attestation = get_valid_attestation(spec, state, signed=True)
|
||||
|
||||
test_vector = get_custody_test_vector(
|
||||
spec.get_custody_chunk_count(attestation.data.crosslink) * spec.BYTES_PER_CUSTODY_CHUNK)
|
||||
shard_root = get_custody_merkle_root(test_vector)
|
||||
attestation.data.crosslink.data_root = shard_root
|
||||
attestation.custody_bits[0] = 0
|
||||
|
||||
state.slot += spec.MIN_ATTESTATION_INCLUSION_DELAY
|
||||
|
||||
_, _, _ = run_attestation_processing(spec, state, attestation)
|
||||
|
||||
state.slot += spec.SLOTS_PER_EPOCH * spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
|
||||
challenge = get_valid_bit_challenge(spec, state, attestation)
|
||||
|
||||
_, _, _ = run_bit_challenge_processing(spec, state, challenge)
|
||||
|
||||
bit_challenge_index = state.custody_challenge_index - 1
|
||||
|
||||
custody_response = get_valid_custody_response(spec, state, challenge, test_vector, bit_challenge_index)
|
||||
|
||||
yield from run_custody_response_processing(spec, state, custody_response)
|
|
@ -0,0 +1,118 @@
|
|||
from eth2spec.test.helpers.custody import get_valid_custody_key_reveal
|
||||
from eth2spec.test.context import (
|
||||
with_all_phases_except,
|
||||
spec_state_test,
|
||||
expect_assertion_error,
|
||||
always_bls,
|
||||
)
|
||||
|
||||
|
||||
def run_custody_key_reveal_processing(spec, state, custody_key_reveal, valid=True):
|
||||
"""
|
||||
Run ``process_custody_key_reveal``, yielding:
|
||||
- pre-state ('pre')
|
||||
- custody_key_reveal ('custody_key_reveal')
|
||||
- post-state ('post').
|
||||
If ``valid == False``, run expecting ``AssertionError``
|
||||
"""
|
||||
yield 'pre', state
|
||||
yield 'custody_key_reveal', custody_key_reveal
|
||||
|
||||
if not valid:
|
||||
expect_assertion_error(lambda: spec.process_custody_key_reveal(state, custody_key_reveal))
|
||||
yield 'post', None
|
||||
return
|
||||
|
||||
revealer_index = custody_key_reveal.revealer_index
|
||||
|
||||
pre_next_custody_secret_to_reveal = \
|
||||
state.validators[revealer_index].next_custody_secret_to_reveal
|
||||
pre_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
|
||||
|
||||
spec.process_custody_key_reveal(state, custody_key_reveal)
|
||||
|
||||
post_next_custody_secret_to_reveal = \
|
||||
state.validators[revealer_index].next_custody_secret_to_reveal
|
||||
post_reveal_lateness = state.validators[revealer_index].max_reveal_lateness
|
||||
|
||||
assert post_next_custody_secret_to_reveal == pre_next_custody_secret_to_reveal + 1
|
||||
|
||||
if spec.get_current_epoch(state) > spec.get_randao_epoch_for_custody_period(
|
||||
pre_next_custody_secret_to_reveal,
|
||||
revealer_index
|
||||
) + spec.EPOCHS_PER_CUSTODY_PERIOD:
|
||||
assert post_reveal_lateness > 0
|
||||
if pre_reveal_lateness == 0:
|
||||
assert post_reveal_lateness == spec.get_current_epoch(state) - spec.get_randao_epoch_for_custody_period(
|
||||
pre_next_custody_secret_to_reveal,
|
||||
revealer_index
|
||||
) - spec.EPOCHS_PER_CUSTODY_PERIOD
|
||||
else:
|
||||
if pre_reveal_lateness > 0:
|
||||
assert post_reveal_lateness < pre_reveal_lateness
|
||||
|
||||
yield 'post', state
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_success(spec, state):
|
||||
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_reveal_too_early(spec, state):
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_wrong_period(spec, state):
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state, period=5)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_late_reveal(spec, state):
|
||||
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_double_reveal(spec, state):
|
||||
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 2
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal, False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
@always_bls
|
||||
@spec_state_test
|
||||
def test_max_decrement(spec, state):
|
||||
state.slot += spec.EPOCHS_PER_CUSTODY_PERIOD * spec.SLOTS_PER_EPOCH * 3 + 150
|
||||
custody_key_reveal = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
_, _, _ = run_custody_key_reveal_processing(spec, state, custody_key_reveal)
|
||||
|
||||
custody_key_reveal2 = get_valid_custody_key_reveal(spec, state)
|
||||
|
||||
yield from run_custody_key_reveal_processing(spec, state, custody_key_reveal2)
|
|
@ -98,25 +98,21 @@ def test_reveal_with_custody_padding_minus_one(spec, state):
|
|||
@never_bls
|
||||
@spec_state_test
|
||||
def test_double_reveal(spec, state):
|
||||
epoch = spec.get_current_epoch(state) + spec.RANDAO_PENALTY_EPOCHS
|
||||
randao_key_reveal1 = get_valid_early_derived_secret_reveal(
|
||||
spec,
|
||||
state,
|
||||
spec.get_current_epoch(state) + spec.RANDAO_PENALTY_EPOCHS + 1,
|
||||
epoch,
|
||||
)
|
||||
res = dict(run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal1))
|
||||
pre_state = res['pre']
|
||||
yield 'pre', pre_state
|
||||
intermediate_state = res['post']
|
||||
_, _, _ = dict(run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal1))
|
||||
|
||||
randao_key_reveal2 = get_valid_early_derived_secret_reveal(
|
||||
spec,
|
||||
intermediate_state,
|
||||
spec.get_current_epoch(pre_state) + spec.RANDAO_PENALTY_EPOCHS + 1,
|
||||
state,
|
||||
epoch,
|
||||
)
|
||||
res = dict(run_early_derived_secret_reveal_processing(spec, intermediate_state, randao_key_reveal2, False))
|
||||
post_state = res['post']
|
||||
yield 'randao_key_reveal', [randao_key_reveal1, randao_key_reveal2]
|
||||
yield 'post', post_state
|
||||
|
||||
yield from run_early_derived_secret_reveal_processing(spec, state, randao_key_reveal2, False)
|
||||
|
||||
|
||||
@with_all_phases_except(['phase0'])
|
||||
|
|
|
@ -5,6 +5,7 @@ bls_active = True
|
|||
|
||||
STUB_SIGNATURE = b'\x11' * 96
|
||||
STUB_PUBKEY = b'\x22' * 48
|
||||
STUB_COORDINATES = bls.api.signature_to_G2(bls.sign(b"", 0, b"\0" * 8))
|
||||
|
||||
|
||||
def only_with_bls(alt_return=None):
|
||||
|
@ -47,3 +48,8 @@ def bls_aggregate_signatures(signatures):
|
|||
def bls_sign(message_hash, privkey, domain):
|
||||
return bls.sign(message_hash=message_hash, privkey=privkey,
|
||||
domain=domain)
|
||||
|
||||
|
||||
@only_with_bls(alt_return=STUB_COORDINATES)
|
||||
def bls_signature_to_G2(signature):
|
||||
return bls.api.signature_to_G2(signature)
|
||||
|
|
|
@ -20,6 +20,13 @@ def calc_merkle_tree_from_leaves(values, layer_count=32):
|
|||
return tree
|
||||
|
||||
|
||||
def get_merkle_tree(values, pad_to=None):
|
||||
layer_count = (len(values) - 1).bit_length() if pad_to is None else (pad_to - 1).bit_length()
|
||||
if len(values) == 0:
|
||||
return zerohashes[layer_count]
|
||||
return calc_merkle_tree_from_leaves(values, layer_count)
|
||||
|
||||
|
||||
def get_merkle_root(values, pad_to=1):
|
||||
if pad_to == 0:
|
||||
return zerohashes[0]
|
||||
|
@ -29,9 +36,9 @@ def get_merkle_root(values, pad_to=1):
|
|||
return calc_merkle_tree_from_leaves(values, layer_count)[-1][0]
|
||||
|
||||
|
||||
def get_merkle_proof(tree, item_index):
|
||||
def get_merkle_proof(tree, item_index, tree_len=None):
|
||||
proof = []
|
||||
for i in range(32):
|
||||
for i in range(tree_len if tree_len is not None else len(tree)):
|
||||
subindex = (item_index // 2**i) ^ 1
|
||||
proof.append(tree[i][subindex] if subindex < len(tree[i]) else zerohashes[i])
|
||||
return proof
|
||||
|
|
Loading…
Reference in New Issue