Merge pull request #2730 from ethereum/proposer-score-boost
Proposer LMD Score Boosting
This commit is contained in:
commit
395fdd4566
|
@ -70,6 +70,11 @@ MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||||
CHURN_LIMIT_QUOTIENT: 65536
|
CHURN_LIMIT_QUOTIENT: 65536
|
||||||
|
|
||||||
|
|
||||||
|
# Fork choice
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 25%
|
||||||
|
PROPOSER_SCORE_BOOST: 70
|
||||||
|
|
||||||
# Deposit contract
|
# Deposit contract
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# Ethereum PoW Mainnet
|
# Ethereum PoW Mainnet
|
||||||
|
|
|
@ -69,6 +69,12 @@ MIN_PER_EPOCH_CHURN_LIMIT: 4
|
||||||
CHURN_LIMIT_QUOTIENT: 32
|
CHURN_LIMIT_QUOTIENT: 32
|
||||||
|
|
||||||
|
|
||||||
|
# Fork choice
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# 25%
|
||||||
|
PROPOSER_SCORE_BOOST: 70
|
||||||
|
|
||||||
|
|
||||||
# Deposit contract
|
# Deposit contract
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# Ethereum Goerli testnet
|
# Ethereum Goerli testnet
|
||||||
|
|
|
@ -175,6 +175,12 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
||||||
# Add new state for this block to the store
|
# Add new state for this block to the store
|
||||||
store.block_states[hash_tree_root(block)] = state
|
store.block_states[hash_tree_root(block)] = state
|
||||||
|
|
||||||
|
# Add proposer score boost if the block is timely
|
||||||
|
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
||||||
|
is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT
|
||||||
|
if get_current_slot(store) == block.slot and is_before_attesting_interval:
|
||||||
|
store.proposer_boost_root = hash_tree_root(block)
|
||||||
|
|
||||||
# Update justified checkpoint
|
# Update justified checkpoint
|
||||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||||
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
|
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
|
||||||
|
|
|
@ -169,7 +169,6 @@ We define the following Python custom types for type hinting and readability:
|
||||||
| `BLSPubkey` | `Bytes48` | a BLS12-381 public key |
|
| `BLSPubkey` | `Bytes48` | a BLS12-381 public key |
|
||||||
| `BLSSignature` | `Bytes96` | a BLS12-381 signature |
|
| `BLSSignature` | `Bytes96` | a BLS12-381 signature |
|
||||||
|
|
||||||
|
|
||||||
## Constants
|
## Constants
|
||||||
|
|
||||||
The following values are (non-configurable) constants used throughout the specification.
|
The following values are (non-configurable) constants used throughout the specification.
|
||||||
|
|
|
@ -7,7 +7,9 @@
|
||||||
|
|
||||||
- [Introduction](#introduction)
|
- [Introduction](#introduction)
|
||||||
- [Fork choice](#fork-choice)
|
- [Fork choice](#fork-choice)
|
||||||
|
- [Constant](#constant)
|
||||||
- [Preset](#preset)
|
- [Preset](#preset)
|
||||||
|
- [Configuration](#configuration)
|
||||||
- [Helpers](#helpers)
|
- [Helpers](#helpers)
|
||||||
- [`LatestMessage`](#latestmessage)
|
- [`LatestMessage`](#latestmessage)
|
||||||
- [`Store`](#store)
|
- [`Store`](#store)
|
||||||
|
@ -56,12 +58,27 @@ Any of the above handlers that trigger an unhandled exception (e.g. a failed ass
|
||||||
4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`.
|
4) **Manual forks**: Manual forks may arbitrarily change the fork choice rule but are expected to be enacted at epoch transitions, with the fork details reflected in `state.fork`.
|
||||||
5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost).
|
5) **Implementation**: The implementation found in this specification is constructed for ease of understanding rather than for optimization in computation, space, or any other resource. A number of optimized alternatives can be found [here](https://github.com/protolambda/lmd-ghost).
|
||||||
|
|
||||||
|
|
||||||
|
### Constant
|
||||||
|
|
||||||
|
| Name | Value |
|
||||||
|
| - | - |
|
||||||
|
| `INTERVALS_PER_SLOT` | `uint64(3)` |
|
||||||
|
|
||||||
### Preset
|
### Preset
|
||||||
|
|
||||||
| Name | Value | Unit | Duration |
|
| Name | Value | Unit | Duration |
|
||||||
| - | - | :-: | :-: |
|
| - | - | :-: | :-: |
|
||||||
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds |
|
| `SAFE_SLOTS_TO_UPDATE_JUSTIFIED` | `2**3` (= 8) | slots | 96 seconds |
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
| Name | Value |
|
||||||
|
| - | - |
|
||||||
|
| `PROPOSER_SCORE_BOOST` | `uint64(70)` |
|
||||||
|
|
||||||
|
- The proposer score boost is worth `PROPOSER_SCORE_BOOST` percentage of the committee's weight, i.e., for slot with committee weight `committee_weight` the boost weight is equal to `(committee_weight * PROPOSER_SCORE_BOOST) // 100`.
|
||||||
|
|
||||||
### Helpers
|
### Helpers
|
||||||
|
|
||||||
#### `LatestMessage`
|
#### `LatestMessage`
|
||||||
|
@ -83,6 +100,7 @@ class Store(object):
|
||||||
justified_checkpoint: Checkpoint
|
justified_checkpoint: Checkpoint
|
||||||
finalized_checkpoint: Checkpoint
|
finalized_checkpoint: Checkpoint
|
||||||
best_justified_checkpoint: Checkpoint
|
best_justified_checkpoint: Checkpoint
|
||||||
|
proposer_boost_root: Root
|
||||||
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
|
blocks: Dict[Root, BeaconBlock] = field(default_factory=dict)
|
||||||
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
|
block_states: Dict[Root, BeaconState] = field(default_factory=dict)
|
||||||
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict)
|
||||||
|
@ -103,12 +121,14 @@ def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -
|
||||||
anchor_epoch = get_current_epoch(anchor_state)
|
anchor_epoch = get_current_epoch(anchor_state)
|
||||||
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
|
justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
|
||||||
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
|
finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root)
|
||||||
|
proposer_boost_root = Root()
|
||||||
return Store(
|
return Store(
|
||||||
time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot),
|
time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot),
|
||||||
genesis_time=anchor_state.genesis_time,
|
genesis_time=anchor_state.genesis_time,
|
||||||
justified_checkpoint=justified_checkpoint,
|
justified_checkpoint=justified_checkpoint,
|
||||||
finalized_checkpoint=finalized_checkpoint,
|
finalized_checkpoint=finalized_checkpoint,
|
||||||
best_justified_checkpoint=justified_checkpoint,
|
best_justified_checkpoint=justified_checkpoint,
|
||||||
|
proposer_boost_root=proposer_boost_root,
|
||||||
blocks={anchor_root: copy(anchor_block)},
|
blocks={anchor_root: copy(anchor_block)},
|
||||||
block_states={anchor_root: copy(anchor_state)},
|
block_states={anchor_root: copy(anchor_state)},
|
||||||
checkpoint_states={justified_checkpoint: copy(anchor_state)},
|
checkpoint_states={justified_checkpoint: copy(anchor_state)},
|
||||||
|
@ -156,11 +176,22 @@ def get_ancestor(store: Store, root: Root, slot: Slot) -> Root:
|
||||||
def get_latest_attesting_balance(store: Store, root: Root) -> Gwei:
|
def get_latest_attesting_balance(store: Store, root: Root) -> Gwei:
|
||||||
state = store.checkpoint_states[store.justified_checkpoint]
|
state = store.checkpoint_states[store.justified_checkpoint]
|
||||||
active_indices = get_active_validator_indices(state, get_current_epoch(state))
|
active_indices = get_active_validator_indices(state, get_current_epoch(state))
|
||||||
return Gwei(sum(
|
attestation_score = Gwei(sum(
|
||||||
state.validators[i].effective_balance for i in active_indices
|
state.validators[i].effective_balance for i in active_indices
|
||||||
if (i in store.latest_messages
|
if (i in store.latest_messages
|
||||||
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
|
and get_ancestor(store, store.latest_messages[i].root, store.blocks[root].slot) == root)
|
||||||
))
|
))
|
||||||
|
proposer_score = Gwei(0)
|
||||||
|
if store.proposer_boost_root != Root():
|
||||||
|
block = store.blocks[root]
|
||||||
|
if get_ancestor(store, root, block.slot) == store.proposer_boost_root:
|
||||||
|
num_validators = len(get_active_validator_indices(state, get_current_epoch(state)))
|
||||||
|
avg_balance = get_total_active_balance(state) // num_validators
|
||||||
|
committee_size = num_validators // SLOTS_PER_EPOCH
|
||||||
|
committee_weight = committee_size * avg_balance
|
||||||
|
proposer_score = (committee_weight * PROPOSER_SCORE_BOOST) // 100
|
||||||
|
return attestation_score + proposer_score
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `filter_block_tree`
|
#### `filter_block_tree`
|
||||||
|
@ -339,6 +370,11 @@ def on_tick(store: Store, time: uint64) -> None:
|
||||||
store.time = time
|
store.time = time
|
||||||
|
|
||||||
current_slot = get_current_slot(store)
|
current_slot = get_current_slot(store)
|
||||||
|
|
||||||
|
# Reset store.proposer_boost_root if this is a new slot
|
||||||
|
if current_slot > previous_slot:
|
||||||
|
store.proposer_boost_root = Root()
|
||||||
|
|
||||||
# Not a new epoch, return
|
# Not a new epoch, return
|
||||||
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
|
if not (current_slot > previous_slot and compute_slots_since_epoch_start(current_slot) == 0):
|
||||||
return
|
return
|
||||||
|
@ -377,6 +413,12 @@ def on_block(store: Store, signed_block: SignedBeaconBlock) -> None:
|
||||||
# Add new state for this block to the store
|
# Add new state for this block to the store
|
||||||
store.block_states[hash_tree_root(block)] = state
|
store.block_states[hash_tree_root(block)] = state
|
||||||
|
|
||||||
|
# Add proposer score boost if the block is timely
|
||||||
|
time_into_slot = (store.time - store.genesis_time) % SECONDS_PER_SLOT
|
||||||
|
is_before_attesting_interval = time_into_slot < SECONDS_PER_SLOT // INTERVALS_PER_SLOT
|
||||||
|
if get_current_slot(store) == block.slot and is_before_attesting_interval:
|
||||||
|
store.proposer_boost_root = hash_tree_root(block)
|
||||||
|
|
||||||
# Update justified checkpoint
|
# Update justified checkpoint
|
||||||
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
if state.current_justified_checkpoint.epoch > store.justified_checkpoint.epoch:
|
||||||
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
|
if state.current_justified_checkpoint.epoch > store.best_justified_checkpoint.epoch:
|
||||||
|
|
|
@ -446,7 +446,7 @@ def get_block_signature(state: BeaconState, block: BeaconBlock, privkey: int) ->
|
||||||
|
|
||||||
A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`.
|
A validator is expected to create, sign, and broadcast an attestation during each epoch. The `committee`, assigned `index`, and assigned `slot` for which the validator performs this role during an epoch are defined by `get_committee_assignment(state, epoch, validator_index)`.
|
||||||
|
|
||||||
A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid block from the expected block proposer for the assigned `slot` or (b) one-third of the `slot` has transpired (`SECONDS_PER_SLOT / 3` seconds after the start of `slot`) -- whichever comes _first_.
|
A validator should create and broadcast the `attestation` to the associated attestation subnet when either (a) the validator has received a valid block from the expected block proposer for the assigned `slot` or (b) `1 / INTERVALS_PER_SLOT` of the `slot` has transpired (`SECONDS_PER_SLOT / INTERVALS_PER_SLOT` seconds after the start of `slot`) -- whichever comes _first_.
|
||||||
|
|
||||||
*Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded, and should be made.
|
*Note*: Although attestations during `GENESIS_EPOCH` do not count toward FFG finality, these initial attestations do give weight to the fork choice, are rewarded, and should be made.
|
||||||
|
|
||||||
|
@ -569,7 +569,7 @@ def get_aggregate_signature(attestations: Sequence[Attestation]) -> BLSSignature
|
||||||
|
|
||||||
#### Broadcast aggregate
|
#### Broadcast aggregate
|
||||||
|
|
||||||
If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate as a `SignedAggregateAndProof` to the global aggregate channel (`beacon_aggregate_and_proof`) two-thirds of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / 3` seconds after the start of `slot`.
|
If the validator is selected to aggregate (`is_aggregator`), then they broadcast their best aggregate as a `SignedAggregateAndProof` to the global aggregate channel (`beacon_aggregate_and_proof`) `2 / INTERVALS_PER_SLOT` of the way through the `slot`-that is, `SECONDS_PER_SLOT * 2 / INTERVALS_PER_SLOT` seconds after the start of `slot`.
|
||||||
|
|
||||||
Selection proofs are provided in `AggregateAndProof` to prove to the gossip channel that the validator has been selected as an aggregator.
|
Selection proofs are provided in `AggregateAndProof` to prove to the gossip channel that the validator has been selected as an aggregator.
|
||||||
|
|
||||||
|
|
|
@ -157,6 +157,7 @@ def add_block(spec,
|
||||||
'epoch': int(store.best_justified_checkpoint.epoch),
|
'epoch': int(store.best_justified_checkpoint.epoch),
|
||||||
'root': encode_hex(store.best_justified_checkpoint.root),
|
'root': encode_hex(store.best_justified_checkpoint.root),
|
||||||
},
|
},
|
||||||
|
'proposer_boost_root': encode_hex(store.proposer_boost_root),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import random
|
||||||
from eth_utils import encode_hex
|
from eth_utils import encode_hex
|
||||||
|
|
||||||
from eth2spec.test.context import (
|
from eth2spec.test.context import (
|
||||||
|
@ -19,6 +20,7 @@ from eth2spec.test.helpers.fork_choice import (
|
||||||
add_block,
|
add_block,
|
||||||
)
|
)
|
||||||
from eth2spec.test.helpers.state import (
|
from eth2spec.test.helpers.state import (
|
||||||
|
next_slots,
|
||||||
next_epoch,
|
next_epoch,
|
||||||
state_transition_and_sign_block,
|
state_transition_and_sign_block,
|
||||||
)
|
)
|
||||||
|
@ -103,18 +105,23 @@ def test_split_tie_breaker_no_attestations(spec, state):
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
# block at slot 1
|
# Create block at slot 1
|
||||||
block_1_state = genesis_state.copy()
|
block_1_state = genesis_state.copy()
|
||||||
block_1 = build_empty_block_for_next_slot(spec, block_1_state)
|
block_1 = build_empty_block_for_next_slot(spec, block_1_state)
|
||||||
signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1)
|
signed_block_1 = state_transition_and_sign_block(spec, block_1_state, block_1)
|
||||||
yield from tick_and_add_block(spec, store, signed_block_1, test_steps)
|
|
||||||
|
|
||||||
# additional block at slot 1
|
# Create additional block at slot 1
|
||||||
block_2_state = genesis_state.copy()
|
block_2_state = genesis_state.copy()
|
||||||
block_2 = build_empty_block_for_next_slot(spec, block_2_state)
|
block_2 = build_empty_block_for_next_slot(spec, block_2_state)
|
||||||
block_2.body.graffiti = b'\x42' * 32
|
block_2.body.graffiti = b'\x42' * 32
|
||||||
signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2)
|
signed_block_2 = state_transition_and_sign_block(spec, block_2_state, block_2)
|
||||||
yield from tick_and_add_block(spec, store, signed_block_2, test_steps)
|
|
||||||
|
# Tick time past slot 1 so proposer score boost does not apply
|
||||||
|
time = store.genesis_time + (block_2.slot + 1) * spec.config.SECONDS_PER_SLOT
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
|
||||||
|
yield from add_block(spec, store, signed_block_1, test_steps)
|
||||||
|
yield from add_block(spec, store, signed_block_2, test_steps)
|
||||||
|
|
||||||
highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
|
highest_root = max(spec.hash_tree_root(block_1), spec.hash_tree_root(block_2))
|
||||||
assert spec.get_head(store) == highest_root
|
assert spec.get_head(store) == highest_root
|
||||||
|
@ -261,3 +268,67 @@ def test_filtered_block_tree(spec, state):
|
||||||
})
|
})
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
def test_proposer_boost_correct_head(spec, state):
|
||||||
|
test_steps = []
|
||||||
|
genesis_state = state.copy()
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||||
|
yield 'anchor_state', state
|
||||||
|
yield 'anchor_block', anchor_block
|
||||||
|
anchor_root = get_anchor_root(spec, state)
|
||||||
|
assert spec.get_head(store) == anchor_root
|
||||||
|
test_steps.append({
|
||||||
|
'checks': {
|
||||||
|
'head': get_formatted_head_output(spec, store),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
# Build block that serves as head ONLY on timely arrival, and ONLY in that slot
|
||||||
|
state_1 = genesis_state.copy()
|
||||||
|
next_slots(spec, state_1, 3)
|
||||||
|
block_1 = build_empty_block_for_next_slot(spec, state_1)
|
||||||
|
signed_block_1 = state_transition_and_sign_block(spec, state_1, block_1)
|
||||||
|
|
||||||
|
# Build block that serves as current head, and remains the head after block_1.slot
|
||||||
|
state_2 = genesis_state.copy()
|
||||||
|
next_slots(spec, state_2, 2)
|
||||||
|
block_2 = build_empty_block_for_next_slot(spec, state_2)
|
||||||
|
signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2)
|
||||||
|
while spec.hash_tree_root(block_1) >= spec.hash_tree_root(block_2):
|
||||||
|
block_2.body.graffiti = spec.Bytes32(hex(random.getrandbits(8 * 32))[2:].zfill(64))
|
||||||
|
signed_block_2 = state_transition_and_sign_block(spec, state_2.copy(), block_2)
|
||||||
|
assert spec.hash_tree_root(block_1) < spec.hash_tree_root(block_2)
|
||||||
|
|
||||||
|
# Tick to block_1 slot time
|
||||||
|
time = store.genesis_time + block_1.slot * spec.config.SECONDS_PER_SLOT
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
|
||||||
|
# Process block_2
|
||||||
|
yield from add_block(spec, store, signed_block_2, test_steps)
|
||||||
|
assert store.proposer_boost_root == spec.Root()
|
||||||
|
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
||||||
|
|
||||||
|
# Process block_1 on timely arrival
|
||||||
|
# The head should temporarily change to block_1
|
||||||
|
yield from add_block(spec, store, signed_block_1, test_steps)
|
||||||
|
assert store.proposer_boost_root == spec.hash_tree_root(block_1)
|
||||||
|
assert spec.get_head(store) == spec.hash_tree_root(block_1)
|
||||||
|
|
||||||
|
# After block_1.slot, the head should revert to block_2
|
||||||
|
time = store.genesis_time + (block_1.slot + 1) * spec.config.SECONDS_PER_SLOT
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
assert store.proposer_boost_root == spec.Root()
|
||||||
|
assert spec.get_head(store) == spec.hash_tree_root(block_2)
|
||||||
|
|
||||||
|
test_steps.append({
|
||||||
|
'checks': {
|
||||||
|
'head': get_formatted_head_output(spec, store),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
yield 'steps', test_steps
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import random
|
import random
|
||||||
|
from eth_utils import encode_hex
|
||||||
|
|
||||||
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
from eth2spec.utils.ssz.ssz_impl import hash_tree_root
|
||||||
from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets
|
from eth2spec.test.context import MINIMAL, spec_state_test, with_all_phases, with_presets
|
||||||
|
@ -703,3 +704,96 @@ def test_new_finalized_slot_is_justified_checkpoint_ancestor(spec, state):
|
||||||
assert store.justified_checkpoint == another_state.current_justified_checkpoint
|
assert store.justified_checkpoint == another_state.current_justified_checkpoint
|
||||||
|
|
||||||
yield 'steps', test_steps
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
def test_proposer_boost(spec, state):
|
||||||
|
test_steps = []
|
||||||
|
genesis_state = state.copy()
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||||
|
yield 'anchor_state', state
|
||||||
|
yield 'anchor_block', anchor_block
|
||||||
|
|
||||||
|
# Build block that serves as head ONLY on timely arrival, and ONLY in that slot
|
||||||
|
state = genesis_state.copy()
|
||||||
|
next_slots(spec, state, 3)
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||||
|
|
||||||
|
# Process block on timely arrival just before end of boost interval
|
||||||
|
time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT +
|
||||||
|
spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT - 1)
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
yield from add_block(spec, store, signed_block, test_steps)
|
||||||
|
assert store.proposer_boost_root == spec.hash_tree_root(block)
|
||||||
|
assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) > 0
|
||||||
|
|
||||||
|
# Ensure that boost is removed after slot is over
|
||||||
|
time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT +
|
||||||
|
spec.config.SECONDS_PER_SLOT)
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
assert store.proposer_boost_root == spec.Root()
|
||||||
|
assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) == 0
|
||||||
|
|
||||||
|
next_slots(spec, state, 3)
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||||
|
|
||||||
|
# Process block on timely arrival at start of boost interval
|
||||||
|
time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT)
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
yield from add_block(spec, store, signed_block, test_steps)
|
||||||
|
assert store.proposer_boost_root == spec.hash_tree_root(block)
|
||||||
|
assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) > 0
|
||||||
|
|
||||||
|
# Ensure that boost is removed after slot is over
|
||||||
|
time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT +
|
||||||
|
spec.config.SECONDS_PER_SLOT)
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
assert store.proposer_boost_root == spec.Root()
|
||||||
|
assert spec.get_latest_attesting_balance(store, spec.hash_tree_root(block)) == 0
|
||||||
|
|
||||||
|
test_steps.append({
|
||||||
|
'checks': {
|
||||||
|
'proposer_boost_root': encode_hex(store.proposer_boost_root),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
yield 'steps', test_steps
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
def test_proposer_boost_root_same_slot_untimely_block(spec, state):
|
||||||
|
test_steps = []
|
||||||
|
genesis_state = state.copy()
|
||||||
|
|
||||||
|
# Initialization
|
||||||
|
store, anchor_block = get_genesis_forkchoice_store_and_block(spec, state)
|
||||||
|
yield 'anchor_state', state
|
||||||
|
yield 'anchor_block', anchor_block
|
||||||
|
|
||||||
|
# Build block that serves as head ONLY on timely arrival, and ONLY in that slot
|
||||||
|
state = genesis_state.copy()
|
||||||
|
next_slots(spec, state, 3)
|
||||||
|
block = build_empty_block_for_next_slot(spec, state)
|
||||||
|
signed_block = state_transition_and_sign_block(spec, state, block)
|
||||||
|
|
||||||
|
# Process block on untimely arrival in the same slot
|
||||||
|
time = (store.genesis_time + block.slot * spec.config.SECONDS_PER_SLOT +
|
||||||
|
spec.config.SECONDS_PER_SLOT // spec.INTERVALS_PER_SLOT)
|
||||||
|
on_tick_and_append_step(spec, store, time, test_steps)
|
||||||
|
yield from add_block(spec, store, signed_block, test_steps)
|
||||||
|
|
||||||
|
assert store.proposer_boost_root == spec.Root()
|
||||||
|
|
||||||
|
test_steps.append({
|
||||||
|
'checks': {
|
||||||
|
'proposer_boost_root': encode_hex(store.proposer_boost_root),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
yield 'steps', test_steps
|
||||||
|
|
|
@ -74,3 +74,10 @@ def test_time(spec, state):
|
||||||
@spec_state_test
|
@spec_state_test
|
||||||
def test_networking(spec, state):
|
def test_networking(spec, state):
|
||||||
assert spec.RANDOM_SUBNETS_PER_VALIDATOR <= spec.ATTESTATION_SUBNET_COUNT
|
assert spec.RANDOM_SUBNETS_PER_VALIDATOR <= spec.ATTESTATION_SUBNET_COUNT
|
||||||
|
|
||||||
|
|
||||||
|
@with_all_phases
|
||||||
|
@spec_state_test
|
||||||
|
def test_fork_choice(spec, state):
|
||||||
|
assert spec.INTERVALS_PER_SLOT < spec.config.SECONDS_PER_SLOT
|
||||||
|
assert spec.config.PROPOSER_SCORE_BOOST <= 100
|
||||||
|
|
|
@ -110,6 +110,7 @@ best_justified_checkpoint: {
|
||||||
epoch: int, -- Integer value from store.best_justified_checkpoint.epoch
|
epoch: int, -- Integer value from store.best_justified_checkpoint.epoch
|
||||||
root: string, -- Encoded 32-byte value from store.best_justified_checkpoint.root
|
root: string, -- Encoded 32-byte value from store.best_justified_checkpoint.root
|
||||||
}
|
}
|
||||||
|
proposer_boost_root: string -- Encoded 32-byte value from store.proposer_boost_root
|
||||||
```
|
```
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
@ -120,6 +121,7 @@ For example:
|
||||||
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
||||||
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
|
finalized_checkpoint: {epoch: 2, root: '0x40d32d6283ec11c53317a46808bc88f55657d93b95a1af920403187accf48f4f'}
|
||||||
best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
best_justified_checkpoint: {epoch: 3, root: '0xc25faab4acab38d3560864ca01e4d5cc4dc2cd473da053fbc03c2669143a2de4'}
|
||||||
|
proposer_boost_root: '0xdaa1d49d57594ced0c35688a6da133abb086d191a2ebdfd736fad95299325aeb'
|
||||||
```
|
```
|
||||||
|
|
||||||
*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store.
|
*Note*: Each `checks` step may include one or multiple items. Each item has to be checked against the current store.
|
||||||
|
|
Loading…
Reference in New Issue