Skip to content

Commit e559bd9

Browse files
realbigseanpaulhauner
authored andcommitted
Store execution block hash in fork choice (#2643)
* - Update the fork choice `ProtoNode` to include `is_merge_complete` - Add database migration for the persisted fork choice * update tests * Small cleanup * lints * store execution block hash in fork choice rather than bool
1 parent 7236dcb commit e559bd9

File tree

9 files changed

+148
-4
lines changed

9 files changed

+148
-4
lines changed

beacon_node/beacon_chain/src/schema_change.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
//! Utilities for managing database schema changes.
2-
use crate::beacon_chain::{BeaconChainTypes, OP_POOL_DB_KEY};
2+
use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY, OP_POOL_DB_KEY};
3+
use crate::persisted_fork_choice::PersistedForkChoice;
34
use crate::validator_pubkey_cache::ValidatorPubkeyCache;
45
use operation_pool::{PersistedOperationPool, PersistedOperationPoolBase};
6+
use proto_array::ProtoArrayForkChoice;
57
use ssz::{Decode, Encode};
68
use ssz_derive::{Decode, Encode};
79
use std::fs;
@@ -93,6 +95,28 @@ pub fn migrate_schema<T: BeaconChainTypes>(
9395

9496
Ok(())
9597
}
98+
// Migration for adding `is_merge_complete` field to the fork choice store.
99+
(SchemaVersion(5), SchemaVersion(6)) => {
100+
let fork_choice_opt = db
101+
.get_item::<PersistedForkChoice>(&FORK_CHOICE_DB_KEY)?
102+
.map(|mut persisted_fork_choice| {
103+
let fork_choice = ProtoArrayForkChoice::from_bytes_legacy(
104+
&persisted_fork_choice.fork_choice.proto_array_bytes,
105+
)?;
106+
persisted_fork_choice.fork_choice.proto_array_bytes = fork_choice.as_bytes();
107+
Ok::<_, String>(persisted_fork_choice)
108+
})
109+
.transpose()
110+
.map_err(StoreError::SchemaMigrationError)?;
111+
if let Some(fork_choice) = fork_choice_opt {
112+
// Store the converted fork choice store under the same key.
113+
db.put_item::<PersistedForkChoice>(&FORK_CHOICE_DB_KEY, &fork_choice)?;
114+
}
115+
116+
db.store_schema_version(to)?;
117+
118+
Ok(())
119+
}
96120
// Anything else is an error.
97121
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
98122
target_version: to,

beacon_node/store/src/metadata.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use ssz::{Decode, Encode};
44
use ssz_derive::{Decode, Encode};
55
use types::{Checkpoint, Hash256, Slot};
66

7-
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(5);
7+
pub const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(6);
88

99
// All the keys that get stored under the `BeaconMeta` column.
1010
//

beacon_node/websocket_server/Cargo.toml

Whitespace-only changes.

beacon_node/websocket_server/src/lib.rs

Whitespace-only changes.

consensus/fork_choice/src/fork_choice.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ pub enum Error<T> {
2323
InvalidBlock(InvalidBlock),
2424
ProtoArrayError(String),
2525
InvalidProtoArrayBytes(String),
26+
InvalidLegacyProtoArrayBytes(String),
2627
MissingProtoArrayBlock(Hash256),
2728
UnknownAncestor {
2829
ancestor_slot: Slot,
@@ -279,6 +280,12 @@ where
279280
AttestationShufflingId::new(anchor_block_root, anchor_state, RelativeEpoch::Next)
280281
.map_err(Error::BeaconStateError)?;
281282

283+
// Default any non-merge execution block hashes to 0x000..000.
284+
let execution_block_hash = anchor_block.message_merge().map_or_else(
285+
|()| Hash256::zero(),
286+
|message| message.body.execution_payload.block_hash,
287+
);
288+
282289
let proto_array = ProtoArrayForkChoice::new(
283290
finalized_block_slot,
284291
finalized_block_state_root,
@@ -287,6 +294,7 @@ where
287294
fc_store.finalized_checkpoint().root,
288295
current_epoch_shuffling_id,
289296
next_epoch_shuffling_id,
297+
execution_block_hash,
290298
)?;
291299

292300
Ok(Self {
@@ -576,6 +584,12 @@ where
576584
.on_verified_block(block, block_root, state)
577585
.map_err(Error::AfterBlockFailed)?;
578586

587+
// Default any non-merge execution block hashes to 0x000..000.
588+
let execution_block_hash = block.body_merge().map_or_else(
589+
|()| Hash256::zero(),
590+
|body| body.execution_payload.block_hash,
591+
);
592+
579593
// This does not apply a vote to the block, it just makes fork choice aware of the block so
580594
// it can still be identified as the head even if it doesn't have any votes.
581595
self.proto_array.process_block(ProtoBlock {
@@ -598,6 +612,7 @@ where
598612
state_root: block.state_root(),
599613
justified_epoch: state.current_justified_checkpoint().epoch,
600614
finalized_epoch: state.finalized_checkpoint().epoch,
615+
execution_block_hash,
601616
})?;
602617

603618
Ok(())
@@ -893,7 +908,7 @@ where
893908
/// This is used when persisting the state of the fork choice to disk.
894909
#[derive(Encode, Decode, Clone)]
895910
pub struct PersistedForkChoice {
896-
proto_array_bytes: Vec<u8>,
911+
pub proto_array_bytes: Vec<u8>,
897912
queued_attestations: Vec<QueuedAttestation>,
898913
}
899914

consensus/proto_array/src/fork_choice_test_definition.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ impl ForkChoiceTestDefinition {
5757
pub fn run(self) {
5858
let junk_shuffling_id =
5959
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
60+
let execution_block_hash = Hash256::zero();
6061
let mut fork_choice = ProtoArrayForkChoice::new(
6162
self.finalized_block_slot,
6263
Hash256::zero(),
@@ -65,6 +66,7 @@ impl ForkChoiceTestDefinition {
6566
self.finalized_root,
6667
junk_shuffling_id.clone(),
6768
junk_shuffling_id,
69+
execution_block_hash,
6870
)
6971
.expect("should create fork choice struct");
7072

@@ -139,6 +141,7 @@ impl ForkChoiceTestDefinition {
139141
),
140142
justified_epoch,
141143
finalized_epoch,
144+
execution_block_hash,
142145
};
143146
fork_choice.process_block(block).unwrap_or_else(|e| {
144147
panic!(

consensus/proto_array/src/proto_array.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,52 @@ pub struct ProtoNode {
3535
best_child: Option<usize>,
3636
#[ssz(with = "four_byte_option_usize")]
3737
best_descendant: Option<usize>,
38+
/// It's necessary to track this so that we can refuse to propagate post-merge blocks without
39+
/// execution payloads, without confusing these with pre-merge blocks.
40+
///
41+
/// Relevant spec issue: https://github.com/ethereum/consensus-specs/issues/2618
42+
pub execution_block_hash: Hash256,
43+
}
44+
45+
/// Only used for SSZ deserialization of the persisted fork choice during the database migration
46+
/// from schema 4 to schema 5.
47+
#[derive(Encode, Decode)]
48+
pub struct LegacyProtoNode {
49+
pub slot: Slot,
50+
pub state_root: Hash256,
51+
pub target_root: Hash256,
52+
pub current_epoch_shuffling_id: AttestationShufflingId,
53+
pub next_epoch_shuffling_id: AttestationShufflingId,
54+
pub root: Hash256,
55+
#[ssz(with = "four_byte_option_usize")]
56+
pub parent: Option<usize>,
57+
pub justified_epoch: Epoch,
58+
pub finalized_epoch: Epoch,
59+
weight: u64,
60+
#[ssz(with = "four_byte_option_usize")]
61+
best_child: Option<usize>,
62+
#[ssz(with = "four_byte_option_usize")]
63+
best_descendant: Option<usize>,
64+
}
65+
66+
impl Into<ProtoNode> for LegacyProtoNode {
67+
fn into(self) -> ProtoNode {
68+
ProtoNode {
69+
slot: self.slot,
70+
state_root: self.state_root,
71+
target_root: self.target_root,
72+
current_epoch_shuffling_id: self.current_epoch_shuffling_id,
73+
next_epoch_shuffling_id: self.next_epoch_shuffling_id,
74+
root: self.root,
75+
parent: self.parent,
76+
justified_epoch: self.justified_epoch,
77+
finalized_epoch: self.finalized_epoch,
78+
weight: self.weight,
79+
best_child: self.best_child,
80+
best_descendant: self.best_descendant,
81+
execution_block_hash: Hash256::zero(),
82+
}
83+
}
3884
}
3985

4086
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
@@ -178,6 +224,7 @@ impl ProtoArray {
178224
weight: 0,
179225
best_child: None,
180226
best_descendant: None,
227+
execution_block_hash: block.execution_block_hash,
181228
};
182229

183230
self.indices.insert(node.root, node_index);

consensus/proto_array/src/proto_array_fork_choice.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::error::Error;
22
use crate::proto_array::ProtoArray;
3-
use crate::ssz_container::SszContainer;
3+
use crate::ssz_container::{LegacySszContainer, SszContainer};
44
use ssz::{Decode, Encode};
55
use ssz_derive::{Decode, Encode};
66
use std::collections::HashMap;
@@ -29,6 +29,7 @@ pub struct Block {
2929
pub next_epoch_shuffling_id: AttestationShufflingId,
3030
pub justified_epoch: Epoch,
3131
pub finalized_epoch: Epoch,
32+
pub execution_block_hash: Hash256,
3233
}
3334

3435
/// A Vec-wrapper which will grow to match any request.
@@ -66,6 +67,7 @@ pub struct ProtoArrayForkChoice {
6667
}
6768

6869
impl ProtoArrayForkChoice {
70+
#[allow(clippy::too_many_arguments)]
6971
pub fn new(
7072
finalized_block_slot: Slot,
7173
finalized_block_state_root: Hash256,
@@ -74,6 +76,7 @@ impl ProtoArrayForkChoice {
7476
finalized_root: Hash256,
7577
current_epoch_shuffling_id: AttestationShufflingId,
7678
next_epoch_shuffling_id: AttestationShufflingId,
79+
execution_block_hash: Hash256,
7780
) -> Result<Self, String> {
7881
let mut proto_array = ProtoArray {
7982
prune_threshold: DEFAULT_PRUNE_THRESHOLD,
@@ -95,6 +98,7 @@ impl ProtoArrayForkChoice {
9598
next_epoch_shuffling_id,
9699
justified_epoch,
97100
finalized_epoch,
101+
execution_block_hash,
98102
};
99103

100104
proto_array
@@ -204,6 +208,7 @@ impl ProtoArrayForkChoice {
204208
next_epoch_shuffling_id: block.next_epoch_shuffling_id.clone(),
205209
justified_epoch: block.justified_epoch,
206210
finalized_epoch: block.finalized_epoch,
211+
execution_block_hash: block.execution_block_hash,
207212
})
208213
}
209214

@@ -252,6 +257,22 @@ impl ProtoArrayForkChoice {
252257
.map_err(|e| format!("Failed to decode ProtoArrayForkChoice: {:?}", e))
253258
}
254259

260+
/// Only used for SSZ deserialization of the persisted fork choice during the database migration
261+
/// from schema 4 to schema 5.
262+
pub fn from_bytes_legacy(bytes: &[u8]) -> Result<Self, String> {
263+
LegacySszContainer::from_ssz_bytes(bytes)
264+
.map(|legacy_container| {
265+
let container: SszContainer = legacy_container.into();
266+
container.into()
267+
})
268+
.map_err(|e| {
269+
format!(
270+
"Failed to decode ProtoArrayForkChoice during schema migration: {:?}",
271+
e
272+
)
273+
})
274+
}
275+
255276
/// Returns a read-lock to core `ProtoArray` struct.
256277
///
257278
/// Should only be used when encoding/decoding during troubleshooting.
@@ -351,6 +372,7 @@ mod test_compute_deltas {
351372
let unknown = Hash256::from_low_u64_be(4);
352373
let junk_shuffling_id =
353374
AttestationShufflingId::from_components(Epoch::new(0), Hash256::zero());
375+
let execution_block_hash = Hash256::zero();
354376

355377
let mut fc = ProtoArrayForkChoice::new(
356378
genesis_slot,
@@ -360,6 +382,7 @@ mod test_compute_deltas {
360382
finalized_root,
361383
junk_shuffling_id.clone(),
362384
junk_shuffling_id.clone(),
385+
execution_block_hash,
363386
)
364387
.unwrap();
365388

@@ -375,6 +398,7 @@ mod test_compute_deltas {
375398
next_epoch_shuffling_id: junk_shuffling_id.clone(),
376399
justified_epoch: genesis_epoch,
377400
finalized_epoch: genesis_epoch,
401+
execution_block_hash,
378402
})
379403
.unwrap();
380404

@@ -390,6 +414,7 @@ mod test_compute_deltas {
390414
next_epoch_shuffling_id: junk_shuffling_id,
391415
justified_epoch: genesis_epoch,
392416
finalized_epoch: genesis_epoch,
417+
execution_block_hash,
393418
})
394419
.unwrap();
395420

consensus/proto_array/src/ssz_container.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::proto_array::LegacyProtoNode;
12
use crate::{
23
proto_array::{ProtoArray, ProtoNode},
34
proto_array_fork_choice::{ElasticList, ProtoArrayForkChoice, VoteTracker},
@@ -17,6 +18,35 @@ pub struct SszContainer {
1718
indices: Vec<(Hash256, usize)>,
1819
}
1920

21+
/// Only used for SSZ deserialization of the persisted fork choice during the database migration
22+
/// from schema 4 to schema 5.
23+
#[derive(Encode, Decode)]
24+
pub struct LegacySszContainer {
25+
votes: Vec<VoteTracker>,
26+
balances: Vec<u64>,
27+
prune_threshold: usize,
28+
justified_epoch: Epoch,
29+
finalized_epoch: Epoch,
30+
nodes: Vec<LegacyProtoNode>,
31+
indices: Vec<(Hash256, usize)>,
32+
}
33+
34+
impl Into<SszContainer> for LegacySszContainer {
35+
fn into(self) -> SszContainer {
36+
let nodes = self.nodes.into_iter().map(Into::into).collect();
37+
38+
SszContainer {
39+
votes: self.votes,
40+
balances: self.balances,
41+
prune_threshold: self.prune_threshold,
42+
justified_epoch: self.justified_epoch,
43+
finalized_epoch: self.finalized_epoch,
44+
nodes,
45+
indices: self.indices,
46+
}
47+
}
48+
}
49+
2050
impl From<&ProtoArrayForkChoice> for SszContainer {
2151
fn from(from: &ProtoArrayForkChoice) -> Self {
2252
let proto_array = &from.proto_array;

0 commit comments

Comments
 (0)