-
Notifications
You must be signed in to change notification settings - Fork 212
Expand file tree
/
Copy pathfork_choice.rs
More file actions
199 lines (171 loc) · 6.49 KB
/
Copy pathfork_choice.rs
File metadata and controls
199 lines (171 loc) · 6.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use ethrex_common::{
types::{BlockHash, BlockHeader, BlockNumber},
H256,
};
use ethrex_storage::{error::StoreError, Store};
use crate::{
error::{self, InvalidForkChoice},
is_canonical,
};
/// Applies new fork choice data to the current blockchain. It performs validity checks:
/// - The finalized, safe and head hashes must correspond to already saved blocks.
/// - The saved blocks should be in the correct order (finalized <= safe <= head).
/// - They must be connected.
///
/// After the validity checks, the canonical chain is updated so that all head's ancestors
/// and itself are made canonical.
///
/// If the fork choice state is applied correctly, the head block header is returned.
pub async fn apply_fork_choice(
store: &Store,
head_hash: H256,
safe_hash: H256,
finalized_hash: H256,
) -> Result<BlockHeader, InvalidForkChoice> {
if head_hash.is_zero() {
return Err(InvalidForkChoice::InvalidHeadHash);
}
let finalized_res = if !finalized_hash.is_zero() {
store.get_block_header_by_hash(finalized_hash)?
} else {
None
};
let safe_res = if !safe_hash.is_zero() {
store.get_block_header_by_hash(safe_hash)?
} else {
None
};
let head_res = store.get_block_header_by_hash(head_hash)?;
if !safe_hash.is_zero() {
check_order(&safe_res, &head_res)?;
}
if !finalized_hash.is_zero() && !safe_hash.is_zero() {
check_order(&finalized_res, &safe_res)?;
}
let Some(head) = head_res else {
return Err(InvalidForkChoice::Syncing);
};
let latest = store.get_latest_block_number().await?;
// If the head block is an already present head ancestor, skip the update.
if is_canonical(store, head.number, head_hash).await? && head.number < latest {
return Err(InvalidForkChoice::NewHeadAlreadyCanonical);
}
// Find blocks that will be part of the new canonical chain.
let Some(new_canonical_blocks) = find_link_with_canonical_chain(store, &head).await? else {
return Err(InvalidForkChoice::Disconnected(
error::ForkChoiceElement::Head,
error::ForkChoiceElement::Safe,
));
};
let link_block_number = match new_canonical_blocks.last() {
Some((number, _)) => *number,
None => head.number,
};
// Check that finalized and safe blocks are part of the new canonical chain.
if let Some(ref finalized) = finalized_res {
if !((is_canonical(store, finalized.number, finalized_hash).await?
&& finalized.number <= link_block_number)
|| (finalized.number == head.number && finalized_hash == head_hash)
|| new_canonical_blocks.contains(&(finalized.number, finalized_hash)))
{
return Err(InvalidForkChoice::Disconnected(
error::ForkChoiceElement::Head,
error::ForkChoiceElement::Finalized,
));
};
}
if let Some(ref safe) = safe_res {
if !((is_canonical(store, safe.number, safe_hash).await?
&& safe.number <= link_block_number)
|| (safe.number == head.number && safe_hash == head_hash)
|| new_canonical_blocks.contains(&(safe.number, safe_hash)))
{
return Err(InvalidForkChoice::Disconnected(
error::ForkChoiceElement::Head,
error::ForkChoiceElement::Safe,
));
};
}
// Finished all validations.
// Make all ancestors to head canonical.
for (number, hash) in new_canonical_blocks {
store.set_canonical_block(number, hash).await?;
}
// Remove anything after the head from the canonical chain.
for number in (head.number + 1)..(latest + 1) {
store.unset_canonical_block(number).await?;
}
// Make head canonical and label all special blocks correctly.
store.set_canonical_block(head.number, head_hash).await?;
if let Some(finalized) = finalized_res {
store
.update_finalized_block_number(finalized.number)
.await?;
}
if let Some(safe) = safe_res {
store.update_safe_block_number(safe.number).await?;
}
store.update_latest_block_number(head.number).await?;
store.update_sync_status(true).await?;
Ok(head)
}
// Checks that block 1 is prior to block 2 and that if the second is present, the first one is too.
fn check_order(
block_1: &Option<BlockHeader>,
block_2: &Option<BlockHeader>,
) -> Result<(), InvalidForkChoice> {
// We don't need to perform the check if the hashes are null
match (block_1, block_2) {
(None, Some(_)) => Err(InvalidForkChoice::ElementNotFound(
error::ForkChoiceElement::Finalized,
)),
(Some(b1), Some(b2)) => {
if b1.number > b2.number {
Err(InvalidForkChoice::Unordered)
} else {
Ok(())
}
}
_ => Err(InvalidForkChoice::Syncing),
}
}
// Find branch of the blockchain connecting a block with the canonical chain. Returns the
// number-hash pairs representing all blocks in that brunch. If genesis is reached and the link
// hasn't been found, an error is returned.
//
// Return values:
// - Err(StoreError): a db-related error happened.
// - Ok(None): The block is not connected to the canonical chain.
// - Ok(Some([])): the block is already canonical.
// - Ok(Some(branch)): the "branch" is a sequence of blocks that connects the ancestor and the
// descendant.
async fn find_link_with_canonical_chain(
store: &Store,
block: &BlockHeader,
) -> Result<Option<Vec<(BlockNumber, BlockHash)>>, StoreError> {
let mut block_number = block.number;
let block_hash = block.compute_block_hash();
let mut header = block.clone();
let mut branch = Vec::new();
if is_canonical(store, block_number, block_hash).await? {
return Ok(Some(branch));
}
let genesis_number = store.get_earliest_block_number().await?;
while block_number > genesis_number {
block_number -= 1;
let parent_hash = header.parent_hash;
// Check that the parent exists.
let parent_header = match store.get_block_header_by_hash(parent_hash) {
Ok(Some(header)) => header,
Ok(None) => return Ok(None),
Err(error) => return Err(error),
};
if is_canonical(store, block_number, parent_hash).await? {
return Ok(Some(branch));
} else {
branch.push((block_number, parent_hash));
}
header = parent_header;
}
Ok(None)
}