-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Fix fork choice #10837
Fix fork choice #10837
Changes from all commits
da8c213
26f80c2
cc49a0d
b831e5a
0d29363
55f3c6f
d3322cb
c312b2d
315893d
de0207b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -710,6 +710,10 @@ impl BlockChain { | |
/// | ||
/// If the tree route verges into pruned or unknown blocks, | ||
/// `None` is returned. | ||
/// | ||
/// `is_from_route_finalized` returns whether the `from` part of the | ||
/// route contains a finalized block. This only holds if the two parts (from | ||
/// and to) are on different branches, ie. on 2 different forks. | ||
pub fn tree_route(&self, from: H256, to: H256) -> Option<TreeRoute> { | ||
let mut from_branch = vec![]; | ||
let mut is_from_route_finalized = false; | ||
|
@@ -723,9 +727,9 @@ impl BlockChain { | |
// reset from && to to the same level | ||
while from_details.number > to_details.number { | ||
from_branch.push(current_from); | ||
is_from_route_finalized = is_from_route_finalized || from_details.is_finalized; | ||
current_from = from_details.parent.clone(); | ||
from_details = self.block_details(&from_details.parent)?; | ||
is_from_route_finalized = is_from_route_finalized || from_details.is_finalized; | ||
} | ||
|
||
while to_details.number > from_details.number { | ||
|
@@ -739,9 +743,9 @@ impl BlockChain { | |
// move to shared parent | ||
while current_from != current_to { | ||
from_branch.push(current_from); | ||
is_from_route_finalized = is_from_route_finalized || from_details.is_finalized; | ||
current_from = from_details.parent.clone(); | ||
from_details = self.block_details(&from_details.parent)?; | ||
is_from_route_finalized = is_from_route_finalized || from_details.is_finalized; | ||
|
||
to_branch.push(current_to); | ||
current_to = to_details.parent.clone(); | ||
|
@@ -2503,4 +2507,74 @@ mod tests { | |
assert_eq!(bc.epoch_transition_for(fork_hash).unwrap().block_number, 0); | ||
} | ||
} | ||
|
||
#[test] | ||
fn tree_rout_with_finalization() { | ||
let genesis = BlockBuilder::genesis(); | ||
let a = genesis.add_block(); | ||
// First branch | ||
let a1 = a.add_block_with_random_transactions(); | ||
let a2 = a1.add_block_with_random_transactions(); | ||
let a3 = a2.add_block_with_random_transactions(); | ||
// Second branch | ||
let b1 = a.add_block_with_random_transactions(); | ||
let b2 = b1.add_block_with_random_transactions(); | ||
|
||
let a_hash = a.last().hash(); | ||
let a1_hash = a1.last().hash(); | ||
let a2_hash = a2.last().hash(); | ||
let a3_hash = a3.last().hash(); | ||
let b2_hash = b2.last().hash(); | ||
|
||
let bootstrap_chain = |blocks: Vec<&BlockBuilder>| { | ||
let db = new_db(); | ||
let bc = new_chain(genesis.last().encoded(), db.clone()); | ||
let mut batch = db.key_value().transaction(); | ||
for block in blocks { | ||
insert_block_batch(&mut batch, &bc, block.last().encoded(), vec![]); | ||
bc.commit(); | ||
} | ||
db.key_value().write(batch).unwrap(); | ||
(db, bc) | ||
}; | ||
|
||
let mark_finalized = |block_hash: H256, db: &Arc<dyn BlockChainDB>, bc: &BlockChain| { | ||
let mut batch = db.key_value().transaction(); | ||
bc.mark_finalized(&mut batch, block_hash).unwrap(); | ||
bc.commit(); | ||
db.key_value().write(batch).unwrap(); | ||
}; | ||
|
||
// Case 1: fork, with finalized common ancestor | ||
{ | ||
let (db, bc) = bootstrap_chain(vec![&a, &a1, &a2, &a3, &b1, &b2]); | ||
assert_eq!(bc.best_block_hash(), a3_hash); | ||
assert_eq!(bc.block_hash(2).unwrap(), a1_hash); | ||
|
||
mark_finalized(a_hash, &db, &bc); | ||
assert!(!bc.tree_route(a3_hash, b2_hash).unwrap().is_from_route_finalized); | ||
assert!(!bc.tree_route(b2_hash, a3_hash).unwrap().is_from_route_finalized); | ||
} | ||
|
||
// Case 2: fork with a finalized block on a branch | ||
{ | ||
let (db, bc) = bootstrap_chain(vec![&a, &a1, &a2, &a3, &b1, &b2]); | ||
assert_eq!(bc.best_block_hash(), a3_hash); | ||
assert_eq!(bc.block_hash(2).unwrap(), a1_hash); | ||
|
||
mark_finalized(a2_hash, &db, &bc); | ||
assert!(bc.tree_route(a3_hash, b2_hash).unwrap().is_from_route_finalized); | ||
assert!(!bc.tree_route(b2_hash, a3_hash).unwrap().is_from_route_finalized); | ||
} | ||
|
||
// Case 3: no-fork, with a finalized block | ||
{ | ||
let (db, bc) = bootstrap_chain(vec![&a, &a1, &a2]); | ||
assert_eq!(bc.best_block_hash(), a2_hash); | ||
|
||
mark_finalized(a1_hash, &db, &bc); | ||
assert!(!bc.tree_route(a1_hash, a2_hash).unwrap().is_from_route_finalized); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we document this counter intuitive behavior in the function docs? A small There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep, I added a comment to the |
||
assert!(!bc.tree_route(a2_hash, a1_hash).unwrap().is_from_route_finalized); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,11 +21,13 @@ use ethereum_types::{U256, H256, Bloom}; | |
|
||
use common_types::encoded; | ||
use common_types::header::Header; | ||
use common_types::transaction::SignedTransaction; | ||
use common_types::transaction::{SignedTransaction, Transaction, Action}; | ||
use common_types::view; | ||
use common_types::views::BlockView; | ||
use keccak_hash::keccak; | ||
use rlp::encode; | ||
use rlp_derive::RlpEncodable; | ||
use triehash_ethereum::ordered_trie_root; | ||
|
||
/// Helper structure, used for encoding blocks. | ||
#[derive(Default, Clone, RlpEncodable)] | ||
|
@@ -136,6 +138,29 @@ impl BlockBuilder { | |
}) | ||
} | ||
|
||
/// Add a block with randomly generated transactions. | ||
#[inline] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a test helper yes? Is it performance sensitive? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah I think it's only used for tests. I just followed the structure of the other similar methods, so I'm not sure whether |
||
pub fn add_block_with_random_transactions(&self) -> Self { | ||
// Maximum of ~50 transactions | ||
let count = rand::random::<u8>() as usize / 5; | ||
let transactions = std::iter::repeat_with(|| { | ||
let data_len = rand::random::<u8>(); | ||
let data = std::iter::repeat_with(|| rand::random::<u8>()) | ||
.take(data_len as usize) | ||
.collect::<Vec<_>>(); | ||
Transaction { | ||
nonce: 0.into(), | ||
gas_price: 0.into(), | ||
gas: 100_000.into(), | ||
action: Action::Create, | ||
value: 100.into(), | ||
data, | ||
}.sign(&keccak("").into(), None) | ||
}).take(count); | ||
|
||
self.add_block_with_transactions(transactions) | ||
} | ||
|
||
/// Add a block with given transactions. | ||
#[inline] | ||
pub fn add_block_with_transactions<T>(&self, transactions: T) -> Self | ||
|
@@ -166,11 +191,15 @@ impl BlockBuilder { | |
let mut block = Block::default(); | ||
let metadata = get_metadata(); | ||
let block_number = parent_number + 1; | ||
let transactions = metadata.transactions; | ||
let transactions_root = ordered_trie_root(transactions.iter().map(rlp::encode)); | ||
|
||
block.header.set_parent_hash(parent_hash); | ||
block.header.set_number(block_number); | ||
block.header.set_log_bloom(metadata.bloom); | ||
block.header.set_difficulty(metadata.difficulty); | ||
block.transactions = metadata.transactions; | ||
block.header.set_transactions_root(transactions_root); | ||
block.transactions = transactions; | ||
|
||
parent_hash = block.hash(); | ||
parent_number = block_number; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be it be a dev dep?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, actually the
generator.rs
module is not for tests only