Fork choice and metadata framework for Engine (#8401)

* Add light client TODO item

* Move existing total-difficulty-based fork choice check to Engine

* Abstract total difficulty and block provider as Machine::BlockMetadata and Machine::BlockProvider

* Decouple "generate_metadata" logic to Engine

* Use fixed BlockMetadata and BlockProvider type for null and instantseal

In this way they can use total difficulty fork choice check

* Extend blockdetails with metadatas and finalized info

* Extra data update: mark_finalized and update_metadatas

* Check finalized block in Blockchain

* Fix a test constructor in verification mod

* Add total difficulty trait

* Fix type import

* Db migration to V13 with metadata column

* Address grumbles

* metadatas -> metadata

* Use generic type for update_metadata to avoid passing HashMap all around

* Remove metadata in blockdetails

* [WIP] Implement a generic metadata architecture

* [WIP] Metadata insertion logic in BlockChain

* typo: Value -> Self::Value

* [WIP] Temporarily remove Engine::is_new_best interface

So that we don't have too many type errors.

* [WIP] Fix more type errors

* [WIP] ExtendedHeader::PartialEq

* [WIP] Change metadata type Option<Vec<u8>> to Vec<u8>

* [WIP] Remove Metadata Error

* [WIP] Clean up error conversion

* [WIP] finalized -> is_finalized

* [WIP] Mark all fields in ExtrasInsert as pub

* [WIP] Remove unused import

* [WIP] Keep only local metadata info

* Mark metadata as optional

* [WIP] Revert metadata db change in BlockChain

* [WIP] Put finalization in unclosed state

* Use metadata interface in BlockDetail

* [WIP] Fix current build failures

* [WIP] Remove unused blockmetadata struct

* Remove DB migration info

* [WIP] Typo

* Use ExtendedHeader to implement fork choice check

* Implement is_new_best using Ancestry iterator

* Use expect instead of panic

* [WIP] Add ancestry Engine support via on_new_block

* Fix tests

* Emission of ancestry actions

* use_short_version should take account of metadata

* Engine::is_new_best -> Engine::fork_choice

* Use proper expect format as defined in #1026

* panic -> expect

* ancestry_header -> ancestry_with_metadata

* Boxed iterator -> &mut iterator

* Fix tests

* is_new_best -> primitive_fork_choice

* Document how fork_choice works

* Engine::fork_choice -> Engine::primitive_fork_choice

* comment: clarify types of finalization where Engine::primitive_fork_choice works

* Expose FinalizationInfo to Engine

* Fix tests due to merging

* Remove TotalDifficulty trait

* Do not pass FinalizationInfo to Engine

If there's finalized blocks in from route, choose the old branch without calling `Engine::fork_choice`.

* Fix compile

* Fix unused import

* Remove is_to_route_finalized

When no block reorg passes a finalized block, this variable is always false.

* Address format grumbles

* Fix docs: mark_finalized returns None if block hash is not found

`blockchain` mod does not yet have an Error type, so we still temporarily use None here.

* Fix inaccurate tree_route None expect description
This commit is contained in:
Wei Tang 2018-05-16 14:58:01 +08:00 committed by GitHub
parent 3bb5ad7204
commit 0ecbb3ec02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 592 additions and 141 deletions

View File

@ -445,6 +445,7 @@ impl HeaderChain {
let raw = header.encoded().into_inner();
transaction.put_vec(self.col, &hash[..], raw);
// TODO: For engines when required, use cryptoeconomic guarantees.
let (best_num, is_new_best) = {
let cur_best = self.best_block.read();
if cur_best.total_difficulty < total_difficulty {

View File

@ -31,7 +31,7 @@ use vm::{EnvInfo, LastHashes};
use engines::EthEngine;
use error::{Error, BlockError};
use factory::Factories;
use header::Header;
use header::{Header, ExtendedHeader};
use receipt::{Receipt, TransactionOutcome};
use state::State;
use state_db::StateDB;
@ -94,6 +94,8 @@ pub struct ExecutedBlock {
state: State<StateDB>,
traces: Tracing,
last_hashes: Arc<LastHashes>,
is_finalized: bool,
metadata: Option<Vec<u8>>,
}
impl ExecutedBlock {
@ -112,6 +114,8 @@ impl ExecutedBlock {
Tracing::Disabled
},
last_hashes: last_hashes,
is_finalized: false,
metadata: None,
}
}
@ -206,6 +210,26 @@ impl ::parity_machine::Transactions for ExecutedBlock {
}
}
impl ::parity_machine::Finalizable for ExecutedBlock {
fn is_finalized(&self) -> bool {
self.is_finalized
}
fn mark_finalized(&mut self) {
self.is_finalized = true;
}
}
impl ::parity_machine::WithMetadata for ExecutedBlock {
fn metadata(&self) -> Option<&[u8]> {
self.metadata.as_ref().map(|v| v.as_ref())
}
fn set_metadata(&mut self, value: Option<Vec<u8>>) {
self.metadata = value;
}
}
/// Block that is ready for transactions to be added.
///
/// It's a bit like a Vec<Transaction>, except that whenever a transaction is pushed, we execute it and
@ -224,6 +248,8 @@ pub struct ClosedBlock {
block: ExecutedBlock,
uncle_bytes: Bytes,
unclosed_state: State<StateDB>,
unclosed_finalization_state: bool,
unclosed_metadata: Option<Vec<u8>>,
}
/// Just like `ClosedBlock` except that we can't reopen it and it's faster.
@ -245,7 +271,7 @@ pub struct SealedBlock {
impl<'x> OpenBlock<'x> {
/// Create a new `OpenBlock` ready for transaction pushing.
pub fn new(
pub fn new<'a>(
engine: &'x EthEngine,
factories: Factories,
tracing: bool,
@ -256,6 +282,7 @@ impl<'x> OpenBlock<'x> {
gas_range_target: (U256, U256),
extra_data: Bytes,
is_epoch_begin: bool,
ancestry: &mut Iterator<Item=ExtendedHeader>,
) -> Result<Self, Error> {
let number = parent.number() + 1;
let state = State::from_existing(db, parent.state_root().clone(), engine.account_start_nonce(number), factories)?;
@ -277,7 +304,7 @@ impl<'x> OpenBlock<'x> {
engine.populate_from_parent(&mut r.block.header, parent);
engine.machine().on_new_block(&mut r.block)?;
engine.on_new_block(&mut r.block, is_epoch_begin)?;
engine.on_new_block(&mut r.block, is_epoch_begin, ancestry)?;
Ok(r)
}
@ -387,6 +414,8 @@ impl<'x> OpenBlock<'x> {
let mut s = self;
let unclosed_state = s.block.state.clone();
let unclosed_metadata = s.block.metadata.clone();
let unclosed_finalization_state = s.block.is_finalized;
if let Err(e) = s.engine.on_close_block(&mut s.block) {
warn!("Encountered error on closing the block: {}", e);
@ -410,6 +439,8 @@ impl<'x> OpenBlock<'x> {
block: s.block,
uncle_bytes,
unclosed_state,
unclosed_metadata,
unclosed_finalization_state,
}
}
@ -483,6 +514,8 @@ impl ClosedBlock {
// revert rewards (i.e. set state back at last transaction's state).
let mut block = self.block;
block.state = self.unclosed_state;
block.metadata = self.unclosed_metadata;
block.is_finalized = self.unclosed_finalization_state;
OpenBlock {
block: block,
engine: engine,
@ -588,6 +621,7 @@ fn enact(
last_hashes: Arc<LastHashes>,
factories: Factories,
is_epoch_begin: bool,
ancestry: &mut Iterator<Item=ExtendedHeader>,
) -> Result<LockedBlock, Error> {
{
if ::log::max_log_level() >= ::log::LogLevel::Trace {
@ -608,6 +642,7 @@ fn enact(
(3141562.into(), 31415620.into()),
vec![],
is_epoch_begin,
ancestry,
)?;
b.populate_from(&header);
@ -630,6 +665,7 @@ pub fn enact_verified(
last_hashes: Arc<LastHashes>,
factories: Factories,
is_epoch_begin: bool,
ancestry: &mut Iterator<Item=ExtendedHeader>,
) -> Result<LockedBlock, Error> {
let view = view!(BlockView, &block.bytes);
@ -644,6 +680,7 @@ pub fn enact_verified(
last_hashes,
factories,
is_epoch_begin,
ancestry,
)
}
@ -701,6 +738,7 @@ mod tests {
(3141562.into(), 31415620.into()),
vec![],
false,
&mut Vec::new().into_iter(),
)?;
b.populate_from(&header);
@ -734,7 +772,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(&*spec.engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = OpenBlock::new(&*spec.engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b = b.close_and_lock();
let _ = b.seal(&*spec.engine, vec![]);
}
@ -748,7 +786,7 @@ mod tests {
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap()
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap()
.close_and_lock().seal(engine, vec![]).unwrap();
let orig_bytes = b.rlp_bytes();
let orig_db = b.drain();
@ -772,7 +810,7 @@ mod tests {
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let mut open_block = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let mut open_block = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes.clone(), Address::zero(), (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let mut uncle1_header = Header::new();
uncle1_header.set_extra_data(b"uncle1".to_vec());
let mut uncle2_header = Header::new();
@ -797,4 +835,3 @@ mod tests {
assert!(orig_db.journal_db().keys().iter().filter(|k| orig_db.journal_db().get(k.0) != db.journal_db().get(k.0)).next() == None);
}
}

View File

@ -38,11 +38,12 @@ use blockchain::block_info::{BlockInfo, BlockLocation, BranchBecomingCanonChainD
use blockchain::extras::{BlockReceipts, BlockDetails, TransactionAddress, EPOCH_KEY_PREFIX, EpochTransitions};
use types::blockchain_info::BlockChainInfo;
use types::tree_route::TreeRoute;
use blockchain::update::ExtrasUpdate;
use blockchain::update::{ExtrasUpdate, ExtrasInsert};
use blockchain::{CacheSize, ImportRoute, Config};
use db::{self, Writable, Readable, CacheUpdatePolicy};
use cache_manager::CacheManager;
use encoded;
use engines::ForkChoice;
use engines::epoch::{Transition as EpochTransition, PendingTransition as PendingEpochTransition};
use rayon::prelude::*;
use ansi_term::Colour;
@ -417,6 +418,42 @@ impl<'a> Iterator for AncestryIter<'a> {
}
}
/// An iterator which walks the blockchain towards the genesis, with metadata information.
pub struct AncestryWithMetadataIter<'a> {
current: H256,
chain: &'a BlockChain,
}
impl<'a> Iterator for AncestryWithMetadataIter<'a> {
type Item = ExtendedHeader;
fn next(&mut self) -> Option<ExtendedHeader> {
if self.current.is_zero() {
None
} else {
let details = self.chain.block_details(&self.current);
let header = self.chain.block_header_data(&self.current)
.map(|h| h.decode().expect("Stored block header data is valid RLP; qed"));
match (details, header) {
(Some(details), Some(header)) => {
self.current = details.parent;
Some(ExtendedHeader {
parent_total_difficulty: details.total_difficulty - *header.difficulty(),
is_finalized: details.is_finalized,
metadata: details.metadata,
header: header,
})
},
_ => {
self.current = H256::default();
None
},
}
}
}
}
/// An iterator which walks all epoch transitions.
/// Returns epoch transitions.
pub struct EpochTransitionIter<'a> {
@ -510,6 +547,8 @@ impl BlockChain {
total_difficulty: header.difficulty(),
parent: header.parent_hash(),
children: vec![],
is_finalized: false,
metadata: None,
};
let mut batch = DBTransaction::new();
@ -649,6 +688,7 @@ impl BlockChain {
/// `None` is returned.
pub fn tree_route(&self, from: H256, to: H256) -> Option<TreeRoute> {
let mut from_branch = vec![];
let mut is_from_route_finalized = false;
let mut to_branch = vec![];
let mut from_details = self.block_details(&from)?;
@ -661,6 +701,7 @@ impl BlockChain {
from_branch.push(current_from);
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 {
@ -676,6 +717,7 @@ impl BlockChain {
from_branch.push(current_from);
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();
@ -689,7 +731,8 @@ impl BlockChain {
Some(TreeRoute {
blocks: from_branch,
ancestor: current_from,
index: index
index: index,
is_from_route_finalized: is_from_route_finalized,
})
}
@ -733,7 +776,7 @@ impl BlockChain {
self.prepare_update(batch, ExtrasUpdate {
block_hashes: self.prepare_block_hashes_update(bytes, &info),
block_details: self.prepare_block_details_update(bytes, &info),
block_details: self.prepare_block_details_update(bytes, &info, false, None),
block_receipts: self.prepare_block_receipts_update(receipts, &info),
blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
@ -769,11 +812,14 @@ impl BlockChain {
location: BlockLocation::CanonChain,
};
// TODO [sorpaas] support warp sync insertion of finalization and metadata.
let block_details = BlockDetails {
number: header.number(),
total_difficulty: info.total_difficulty,
parent: header.parent_hash(),
children: Vec::new(),
is_finalized: false,
metadata: None,
};
let mut update = HashMap::new();
@ -898,7 +944,22 @@ impl BlockChain {
/// Inserts the block into backing cache database.
/// Expects the block to be valid and already verified.
/// If the block is already known, does nothing.
pub fn insert_block(&self, batch: &mut DBTransaction, bytes: &[u8], receipts: Vec<Receipt>) -> ImportRoute {
pub fn insert_block(&self, batch: &mut DBTransaction, bytes: &[u8], receipts: Vec<Receipt>, extras: ExtrasInsert) -> ImportRoute {
let block = view!(BlockView, bytes);
let header = block.header_view();
let parent_hash = header.parent_hash();
let best_hash = self.best_block_hash();
let route = self.tree_route(best_hash, parent_hash).expect("forks are only kept when it has common ancestors; tree route from best to prospective's parent always exists; qed");
self.insert_block_with_route(batch, bytes, receipts, route, extras)
}
/// Inserts the block into backing cache database with already generated route information.
/// Expects the block to be valid and already verified and route is tree route information from current best block to new block's parent.
/// If the block is already known, does nothing.
pub fn insert_block_with_route(&self, batch: &mut DBTransaction, bytes: &[u8], receipts: Vec<Receipt>, route: TreeRoute, extras: ExtrasInsert) -> ImportRoute {
// create views onto rlp
let block = view!(BlockView, bytes);
let header = block.header_view();
@ -917,7 +978,7 @@ impl BlockChain {
batch.put(db::COL_HEADERS, &hash, &compressed_header);
batch.put(db::COL_BODIES, &hash, &compressed_body);
let info = self.block_info(&header);
let info = self.block_info(&header, route, &extras);
if let BlockLocation::BranchBecomingCanonChain(ref d) = info.location {
info!(target: "reorg", "Reorg to {} ({} {} {})",
@ -930,7 +991,7 @@ impl BlockChain {
self.prepare_update(batch, ExtrasUpdate {
block_hashes: self.prepare_block_hashes_update(bytes, &info),
block_details: self.prepare_block_details_update(bytes, &info),
block_details: self.prepare_block_details_update(bytes, &info, extras.is_finalized, extras.metadata),
block_receipts: self.prepare_block_receipts_update(receipts, &info),
blocks_blooms: self.prepare_block_blooms_update(bytes, &info),
transactions_addresses: self.prepare_transaction_addresses_update(bytes, &info),
@ -942,27 +1003,21 @@ impl BlockChain {
}
/// Get inserted block info which is critical to prepare extras updates.
fn block_info(&self, header: &HeaderView) -> BlockInfo {
fn block_info(&self, header: &HeaderView, route: TreeRoute, extras: &ExtrasInsert) -> BlockInfo {
let hash = header.hash();
let number = header.number();
let parent_hash = header.parent_hash();
let parent_details = self.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash));
let is_new_best = parent_details.total_difficulty + header.difficulty() > self.best_block_total_difficulty();
BlockInfo {
hash: hash,
number: number,
total_difficulty: parent_details.total_difficulty + header.difficulty(),
location: if is_new_best {
// on new best block we need to make sure that all ancestors
location: match extras.fork_choice {
ForkChoice::New => {
// On new best block we need to make sure that all ancestors
// are moved to "canon chain"
// find the route between old best block and the new one
let best_hash = self.best_block_hash();
let route = self.tree_route(best_hash, parent_hash)
.expect("blocks being imported always within recent history; qed");
assert_eq!(number, parent_details.number + 1);
match route.blocks.len() {
0 => BlockLocation::CanonChain,
_ => {
@ -975,10 +1030,30 @@ impl BlockChain {
})
}
}
} else {
BlockLocation::Branch
},
ForkChoice::Old => BlockLocation::Branch,
},
}
}
/// Mark a block to be considered finalized. Returns `Some(())` if the operation succeeds, and `None` if the block
/// hash is not found.
pub fn mark_finalized(&self, batch: &mut DBTransaction, block_hash: H256) -> Option<()> {
let mut block_details = self.block_details(&block_hash)?;
block_details.is_finalized = true;
self.update_block_details(batch, block_hash, block_details);
Some(())
}
/// Prepares extras block detail update.
fn update_block_details(&self, batch: &mut DBTransaction, block_hash: H256, block_details: BlockDetails) {
let mut details_map = HashMap::new();
details_map.insert(block_hash, block_details);
// We're only updating one existing value. So it shouldn't suffer from cache decoherence problem.
let mut write_details = self.pending_block_details.write();
batch.extend_with_cache(db::COL_EXTRA, &mut *write_details, details_map, CacheUpdatePolicy::Overwrite);
}
/// Prepares extras update.
@ -1101,6 +1176,18 @@ impl BlockChain {
}
}
/// Iterator that lists `first` and then all of `first`'s ancestors, by extended header.
pub fn ancestry_with_metadata_iter<'a>(&'a self, first: H256) -> AncestryWithMetadataIter {
AncestryWithMetadataIter {
current: if self.is_known(&first) {
first
} else {
H256::default() // zero hash
},
chain: self
}
}
/// Given a block's `parent`, find every block header which represents a valid possible uncle.
pub fn find_uncle_headers(&self, parent: &H256, uncle_generations: usize) -> Option<Vec<encoded::Header>> {
self.find_uncle_hashes(parent, uncle_generations)
@ -1166,7 +1253,7 @@ impl BlockChain {
/// This function returns modified block details.
/// Uses the given parent details or attempts to load them from the database.
fn prepare_block_details_update(&self, block_bytes: &[u8], info: &BlockInfo) -> HashMap<H256, BlockDetails> {
fn prepare_block_details_update(&self, block_bytes: &[u8], info: &BlockInfo, is_finalized: bool, metadata: Option<Vec<u8>>) -> HashMap<H256, BlockDetails> {
let block = view!(BlockView, block_bytes);
let header = block.header_view();
let parent_hash = header.parent_hash();
@ -1181,6 +1268,8 @@ impl BlockChain {
total_difficulty: info.total_difficulty,
parent: parent_hash,
children: vec![],
is_finalized: is_finalized,
metadata: metadata,
};
// write to batch
@ -1418,7 +1507,7 @@ mod tests {
use std::sync::Arc;
use rustc_hex::FromHex;
use hash::keccak;
use kvdb::KeyValueDB;
use kvdb::{KeyValueDB, DBTransaction};
use kvdb_memorydb;
use ethereum_types::*;
use receipt::{Receipt, TransactionOutcome};
@ -1441,6 +1530,42 @@ mod tests {
BlockChain::new(Config::default(), genesis, db)
}
fn insert_block(db: &Arc<KeyValueDB>, bc: &BlockChain, bytes: &[u8], receipts: Vec<Receipt>) -> ImportRoute {
insert_block_commit(db, bc, bytes, receipts, true)
}
fn insert_block_commit(db: &Arc<KeyValueDB>, bc: &BlockChain, bytes: &[u8], receipts: Vec<Receipt>, commit: bool) -> ImportRoute {
let mut batch = db.transaction();
let res = insert_block_batch(&mut batch, bc, bytes, receipts);
db.write(batch).unwrap();
if commit {
bc.commit();
}
res
}
fn insert_block_batch(batch: &mut DBTransaction, bc: &BlockChain, bytes: &[u8], receipts: Vec<Receipt>) -> ImportRoute {
use views::BlockView;
use blockchain::ExtrasInsert;
let block = view!(BlockView, bytes);
let header = block.header_view();
let parent_hash = header.parent_hash();
let parent_details = bc.block_details(&parent_hash).unwrap_or_else(|| panic!("Invalid parent hash: {:?}", parent_hash));
let block_total_difficulty = parent_details.total_difficulty + header.difficulty();
let fork_choice = if block_total_difficulty > bc.best_block_total_difficulty() {
::engines::ForkChoice::New
} else {
::engines::ForkChoice::Old
};
bc.insert_block(batch, bytes, receipts, ExtrasInsert {
fork_choice: fork_choice,
is_finalized: false,
metadata: None
})
}
#[test]
fn should_cache_best_block() {
// given
@ -1452,8 +1577,7 @@ mod tests {
assert_eq!(bc.best_block_number(), 0);
// when
let mut batch = db.transaction();
bc.insert_block(&mut batch, &first.last().encoded(), vec![]);
insert_block_commit(&db, &bc, &first.last().encoded(), vec![], false);
assert_eq!(bc.best_block_number(), 0);
bc.commit();
// NOTE no db.write here (we want to check if best block is cached)
@ -1483,7 +1607,7 @@ mod tests {
assert_eq!(bc.block_details(&genesis_hash).unwrap().children, vec![]);
let mut batch = db.transaction();
bc.insert_block(&mut batch, &first.encoded(), vec![]);
insert_block_batch(&mut batch, &bc, &first.encoded(), vec![]);
db.write(batch).unwrap();
bc.commit();
@ -1509,7 +1633,7 @@ mod tests {
let mut batch = db.transaction();
for block in generator {
block_hashes.push(block.hash());
bc.insert_block(&mut batch, &block.encoded(), vec![]);
insert_block_batch(&mut batch, &bc, &block.encoded(), vec![]);
bc.commit();
}
db.write(batch).unwrap();
@ -1549,14 +1673,10 @@ mod tests {
let db = new_db();
let bc = new_chain(&genesis.last().encoded(), db.clone());
let mut batch = db.transaction();
for b in generator {
bc.insert_block(&mut batch, &b.encoded(), vec![]);
bc.commit();
insert_block(&db, &bc, &b.encoded(), vec![]);
}
db.write(batch).unwrap();
assert_eq!(uncle_headers, bc.find_uncle_headers(&b4a_hash, 3).unwrap());
// TODO: insert block that already includes one of them as an uncle to check it's not allowed.
}
@ -1590,9 +1710,9 @@ mod tests {
let bc = new_chain(&genesis.last().encoded(), db.clone());
let mut batch = db.transaction();
let _ = bc.insert_block(&mut batch, &b1a.last().encoded(), vec![]);
let _ = insert_block_batch(&mut batch, &bc, &b1a.last().encoded(), vec![]);
bc.commit();
let _ = bc.insert_block(&mut batch, &b1b.last().encoded(), vec![]);
let _ = insert_block_batch(&mut batch, &bc, &b1b.last().encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
@ -1604,7 +1724,7 @@ mod tests {
// now let's make forked chain the canon chain
let mut batch = db.transaction();
let _ = bc.insert_block(&mut batch, &b2.last().encoded(), vec![]);
let _ = insert_block_batch(&mut batch, &bc, &b2.last().encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
@ -1665,9 +1785,9 @@ mod tests {
let bc = new_chain(&genesis.last().encoded(), db.clone());
let mut batch = db.transaction();
let _ = bc.insert_block(&mut batch, &b1a.last().encoded(), vec![]);
let _ = insert_block_batch(&mut batch, &bc, &b1a.last().encoded(), vec![]);
bc.commit();
let _ = bc.insert_block(&mut batch, &b1b.last().encoded(), vec![]);
let _ = insert_block_batch(&mut batch, &bc, &b1b.last().encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
@ -1683,7 +1803,7 @@ mod tests {
// now let's make forked chain the canon chain
let mut batch = db.transaction();
let _ = bc.insert_block(&mut batch, &b2.last().encoded(), vec![]);
let _ = insert_block_batch(&mut batch, &bc, &b2.last().encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
@ -1723,16 +1843,16 @@ mod tests {
let bc = new_chain(&genesis.last().encoded(), db.clone());
let mut batch = db.transaction();
let ir1 = bc.insert_block(&mut batch, &b1.last().encoded(), vec![]);
let ir1 = insert_block_batch(&mut batch, &bc, &b1.last().encoded(), vec![]);
bc.commit();
let ir2 = bc.insert_block(&mut batch, &b2.last().encoded(), vec![]);
let ir2 = insert_block_batch(&mut batch, &bc, &b2.last().encoded(), vec![]);
bc.commit();
let ir3b = bc.insert_block(&mut batch, &b3b.last().encoded(), vec![]);
let ir3b = insert_block_batch(&mut batch, &bc, &b3b.last().encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
assert_eq!(bc.block_hash(3).unwrap(), b3b_hash);
let mut batch = db.transaction();
let ir3a = bc.insert_block(&mut batch, &b3a.last().encoded(), vec![]);
let ir3a = insert_block_batch(&mut batch, &bc, &b3a.last().encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
@ -1837,7 +1957,7 @@ mod tests {
let bc = new_chain(&genesis.last().encoded(), db.clone());
assert_eq!(bc.best_block_hash(), genesis_hash);
let mut batch = db.transaction();
bc.insert_block(&mut batch, &first.last().encoded(), vec![]);
insert_block_batch(&mut batch, &bc, &first.last().encoded(), vec![]);
db.write(batch).unwrap();
bc.commit();
assert_eq!(bc.best_block_hash(), first_hash);
@ -1896,7 +2016,7 @@ mod tests {
let db = new_db();
let bc = new_chain(&genesis, db.clone());
let mut batch =db.transaction();
bc.insert_block(&mut batch, &b1, vec![]);
insert_block_batch(&mut batch, &bc, &b1, vec![]);
db.write(batch).unwrap();
bc.commit();
@ -1907,14 +2027,6 @@ mod tests {
}
}
fn insert_block(db: &Arc<KeyValueDB>, bc: &BlockChain, bytes: &[u8], receipts: Vec<Receipt>) -> ImportRoute {
let mut batch = db.transaction();
let res = bc.insert_block(&mut batch, bytes, receipts);
db.write(batch).unwrap();
bc.commit();
res
}
#[test]
fn test_logs() {
let t1 = Transaction {
@ -2198,12 +2310,12 @@ mod tests {
let mut batch = db.transaction();
// create a longer fork
for block in generator {
bc.insert_block(&mut batch, &block.encoded(), vec![]);
insert_block_batch(&mut batch, &bc, &block.encoded(), vec![]);
bc.commit();
}
assert_eq!(bc.best_block_number(), 5);
bc.insert_block(&mut batch, &uncle.last().encoded(), vec![]);
insert_block_batch(&mut batch, &bc, &uncle.last().encoded(), vec![]);
db.write(batch).unwrap();
bc.commit();
}
@ -2230,7 +2342,7 @@ mod tests {
// create a longer fork
for (i, block) in generator.into_iter().enumerate() {
bc.insert_block(&mut batch, &block.encoded(), vec![]);
insert_block_batch(&mut batch, &bc, &block.encoded(), vec![]);
bc.insert_epoch_transition(&mut batch, i as u64, EpochTransition {
block_hash: block.hash(),
block_number: i as u64 + 1,
@ -2241,7 +2353,7 @@ mod tests {
assert_eq!(bc.best_block_number(), 5);
bc.insert_block(&mut batch, &uncle.last().encoded(), vec![]);
insert_block_batch(&mut batch, &bc, &uncle.last().encoded(), vec![]);
bc.insert_epoch_transition(&mut batch, 999, EpochTransition {
block_hash: uncle.last().hash(),
block_number: 1,
@ -2291,11 +2403,7 @@ mod tests {
// and a non-canonical fork of 8 from genesis.
let fork_hash = {
for block in fork_generator {
let mut batch = db.transaction();
bc.insert_block(&mut batch, &block.encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
insert_block(&db, &bc, &block.encoded(), vec![]);
}
assert_eq!(bc.best_block_number(), 7);
@ -2303,11 +2411,7 @@ mod tests {
};
for block in next_generator {
let mut batch = db.transaction();
bc.insert_block(&mut batch, &block.encoded(), vec![]);
bc.commit();
db.write(batch).unwrap();
insert_block(&db, &bc, &block.encoded(), vec![]);
}
assert_eq!(bc.best_block_number(), 10);

View File

@ -23,6 +23,7 @@ use db::Key;
use engines::epoch::{Transition as EpochTransition};
use header::BlockNumber;
use receipt::Receipt;
use rlp;
use heapsize::HeapSizeOf;
use ethereum_types::{H256, H264, U256};
@ -167,7 +168,7 @@ impl Key<EpochTransitions> for u64 {
}
/// Familial details concerning a block
#[derive(Debug, Clone, RlpEncodable, RlpDecodable)]
#[derive(Debug, Clone)]
pub struct BlockDetails {
/// Block number
pub number: BlockNumber,
@ -177,6 +178,57 @@ pub struct BlockDetails {
pub parent: H256,
/// List of children block hashes
pub children: Vec<H256>,
/// Whether the block is considered finalized
pub is_finalized: bool,
/// Additional block metadata
pub metadata: Option<Vec<u8>>,
}
impl rlp::Encodable for BlockDetails {
fn rlp_append(&self, stream: &mut rlp::RlpStream) {
let use_short_version = self.metadata.is_none() && !self.is_finalized;
match use_short_version {
true => { stream.begin_list(4); },
false => { stream.begin_list(6); },
}
stream.append(&self.number);
stream.append(&self.total_difficulty);
stream.append(&self.parent);
stream.append_list(&self.children);
if !use_short_version {
stream.append(&self.is_finalized);
stream.append(&self.metadata);
}
}
}
impl rlp::Decodable for BlockDetails {
fn decode(rlp: &rlp::Rlp) -> Result<Self, rlp::DecoderError> {
let use_short_version = match rlp.item_count()? {
4 => true,
6 => false,
_ => return Err(rlp::DecoderError::RlpIncorrectListLen),
};
Ok(BlockDetails {
number: rlp.val_at(0)?,
total_difficulty: rlp.val_at(1)?,
parent: rlp.val_at(2)?,
children: rlp.list_at(3)?,
is_finalized: if use_short_version {
false
} else {
rlp.val_at(4)?
},
metadata: if use_short_version {
None
} else {
rlp.val_at(5)?
},
})
}
}
impl HeapSizeOf for BlockDetails {

View File

@ -33,4 +33,5 @@ pub use self::cache::CacheSize;
pub use self::config::Config;
pub use self::extras::{BlockReceipts, BlockDetails, TransactionAddress};
pub use self::import_route::ImportRoute;
pub use self::update::ExtrasInsert;
pub use types::tree_route::TreeRoute;

View File

@ -22,3 +22,13 @@ pub struct ExtrasUpdate<'a> {
/// Modified transaction addresses (None signifies removed transactions).
pub transactions_addresses: HashMap<H256, Option<TransactionAddress>>,
}
/// Extra information in block insertion.
pub struct ExtrasInsert {
/// The primitive fork choice before applying finalization rules.
pub fork_choice: ::engines::ForkChoice,
/// Is the inserted block considered finalized.
pub is_finalized: bool,
/// New block local metadata.
pub metadata: Option<Vec<u8>>,
}

View File

@ -33,7 +33,7 @@ use util_error::UtilError;
// other
use ethereum_types::{H256, Address, U256};
use block::{IsBlock, LockedBlock, Drain, ClosedBlock, OpenBlock, enact_verified, SealedBlock};
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute, TransactionAddress};
use blockchain::{BlockChain, BlockProvider, TreeRoute, ImportRoute, TransactionAddress, ExtrasInsert};
use client::ancient_import::AncientVerifier;
use client::Error as ClientError;
use client::{
@ -50,13 +50,13 @@ use client::{
IoClient,
};
use encoded;
use engines::{EthEngine, EpochTransition};
use engines::{EthEngine, EpochTransition, ForkChoice};
use error::{ImportErrorKind, BlockImportErrorKind, ExecutionError, CallError, BlockError, ImportResult, Error as EthcoreError};
use vm::{EnvInfo, LastHashes};
use evm::Schedule;
use executive::{Executive, Executed, TransactOptions, contract_address};
use factory::{Factories, VmFactory};
use header::{BlockNumber, Header};
use header::{BlockNumber, Header, ExtendedHeader};
use io::{IoChannel, IoError};
use log_entry::LocalizedLogEntry;
use miner::{Miner, MinerService};
@ -73,10 +73,12 @@ use trace::{TraceDB, ImportRequest as TraceImportRequest, LocalizedTrace, Databa
use transaction::{self, LocalizedTransaction, UnverifiedTransaction, SignedTransaction, Transaction, Action};
use types::filter::Filter;
use types::mode::Mode as IpcMode;
use types::ancestry_action::AncestryAction;
use verification;
use verification::{PreverifiedBlock, Verifier};
use verification::queue::BlockQueue;
use views::BlockView;
use parity_machine::{Finalizable, WithMetadata};
// re-export
pub use types::blockchain_info::BlockChainInfo;
@ -396,6 +398,7 @@ impl Importer {
last_hashes,
client.factories.clone(),
is_epoch_begin,
&mut chain.ancestry_with_metadata_iter(*header.parent_hash()),
);
let mut locked_block = enact_result.map_err(|e| {
@ -467,6 +470,44 @@ impl Importer {
let mut batch = DBTransaction::new();
let ancestry_actions = self.engine.ancestry_actions(block.block(), &mut chain.ancestry_with_metadata_iter(*parent));
let best_hash = chain.best_block_hash();
let metadata = block.block().metadata().map(Into::into);
let is_finalized = block.block().is_finalized();
let new = ExtendedHeader {
header: header.clone(),
is_finalized: is_finalized,
metadata: metadata,
parent_total_difficulty: chain.block_details(&parent).expect("Parent block is in the database; qed").total_difficulty
};
let best = {
let hash = best_hash;
let header = chain.block_header_data(&hash)
.expect("Best block is in the database; qed")
.decode()
.expect("Stored block header is valid RLP; qed");
let details = chain.block_details(&hash)
.expect("Best block is in the database; qed");
ExtendedHeader {
parent_total_difficulty: details.total_difficulty - *header.difficulty(),
is_finalized: details.is_finalized,
metadata: details.metadata,
header: header,
}
};
let route = chain.tree_route(best_hash, *parent).expect("forks are only kept when it has common ancestors; tree route from best to prospective's parent always exists; qed");
let fork_choice = if route.is_from_route_finalized {
ForkChoice::Old
} else {
self.engine.fork_choice(&new, &best)
};
// CHECK! I *think* this is fine, even if the state_root is equal to another
// already-imported block of the same number.
// TODO: Prove it with a test.
@ -485,7 +526,17 @@ impl Importer {
);
state.journal_under(&mut batch, number, hash).expect("DB commit failed");
let route = chain.insert_block(&mut batch, block_data, receipts.clone());
for ancestry_action in ancestry_actions {
let AncestryAction::MarkFinalized(ancestry) = ancestry_action;
chain.mark_finalized(&mut batch, ancestry).expect("Engine's ancestry action must be known blocks; qed");
}
let route = chain.insert_block(&mut batch, block_data, receipts.clone(), ExtrasInsert {
fork_choice: fork_choice,
is_finalized: is_finalized,
metadata: new.metadata,
});
client.tracedb.read().import(&mut batch, TraceImportRequest {
traces: traces.into(),
@ -2069,6 +2120,7 @@ impl PrepareOpenBlock for Client {
gas_range_target,
extra_data,
is_epoch_begin,
&mut chain.ancestry_with_metadata_iter(best_header.hash()),
).expect("OpenBlock::new only fails if parent state root invalid; state root of best block's header is never invalid; qed");
// Add uncles
@ -2284,6 +2336,7 @@ mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use kvdb::DBTransaction;
use blockchain::ExtrasInsert;
let client = generate_dummy_client(0);
let genesis = client.chain_info().best_block_hash;
@ -2296,7 +2349,11 @@ mod tests {
let another_client = client.clone();
thread::spawn(move || {
let mut batch = DBTransaction::new();
another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new());
another_client.chain.read().insert_block(&mut batch, &new_block, Vec::new(), ExtrasInsert {
fork_choice: ::engines::ForkChoice::New,
is_finalized: false,
metadata: None,
});
go_thread.store(true, Ordering::SeqCst);
});
go

View File

@ -392,6 +392,7 @@ impl PrepareOpenBlock for TestBlockChainClient {
gas_range_target,
extra_data,
false,
&mut Vec::new().into_iter(),
).expect("Opening block for tests will not fail.");
// TODO [todr] Override timestamp for predictability
open_block.set_timestamp(*self.latest_block_timestamp.read());
@ -739,7 +740,8 @@ impl BlockChainClient for TestBlockChainClient {
}
}
if adding { Vec::new() } else { blocks }
}
},
is_from_route_finalized: false,
})
}

View File

@ -33,7 +33,7 @@ use error::{Error, BlockError};
use ethjson;
use machine::{AuxiliaryData, Call, EthereumMachine};
use hash::keccak;
use header::{Header, BlockNumber};
use header::{Header, BlockNumber, ExtendedHeader};
use super::signer::EngineSigner;
use super::validator_set::{ValidatorSet, SimpleList, new_validator_set};
@ -957,6 +957,7 @@ impl Engine<EthereumMachine> for AuthorityRound {
&self,
block: &mut ExecutedBlock,
epoch_begin: bool,
_ancestry: &mut Iterator<Item=ExtendedHeader>,
) -> Result<(), Error> {
// with immediate transitions, we don't use the epoch mechanism anyway.
// the genesis is always considered an epoch, but we ignore it intentionally.
@ -1348,6 +1349,10 @@ impl Engine<EthereumMachine> for AuthorityRound {
Some(Box::new(::snapshot::PoaSnapshot))
}
}
fn fork_choice(&self, new: &ExtendedHeader, current: &ExtendedHeader) -> super::ForkChoice {
super::total_difficulty_fork_choice(new, current)
}
}
#[cfg(test)]
@ -1407,9 +1412,9 @@ mod tests {
let db1 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let db2 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b1 = b1.close_and_lock();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b2 = b2.close_and_lock();
engine.set_signer(tap.clone(), addr1, "1".into());
@ -1441,9 +1446,9 @@ mod tests {
let db2 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b1 = b1.close_and_lock();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes, addr2, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b2 = b2.close_and_lock();
engine.set_signer(tap.clone(), addr1, "1".into());
@ -1696,7 +1701,7 @@ mod tests {
let db1 = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b1 = b1.close_and_lock();
let client = generate_dummy_client(0);
@ -1731,7 +1736,7 @@ mod tests {
let last_hashes = Arc::new(vec![genesis_header.hash()]);
// step 2
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b1 = b1.close_and_lock();
// since the block is empty it isn't sealed and we generate empty steps
@ -1740,7 +1745,7 @@ mod tests {
engine.step();
// step 3
let mut b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes.clone(), addr2, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let mut b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes.clone(), addr2, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
b2.push_transaction(Transaction {
action: Action::Create,
nonce: U256::from(0),
@ -1779,7 +1784,7 @@ mod tests {
let last_hashes = Arc::new(vec![genesis_header.hash()]);
// step 2
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b1 = b1.close_and_lock();
// since the block is empty it isn't sealed and we generate empty steps
@ -1788,7 +1793,7 @@ mod tests {
engine.step();
// step 3
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes.clone(), addr2, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes.clone(), addr2, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b2 = b2.close_and_lock();
engine.set_signer(tap.clone(), addr2, "0".into());
assert_eq!(engine.generate_seal(b2.block(), &genesis_header), Seal::None);
@ -1796,7 +1801,7 @@ mod tests {
// step 4
// the spec sets the maximum_empty_steps to 2 so we will now seal an empty block and include the empty step messages
let b3 = OpenBlock::new(engine, Default::default(), false, db3, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b3 = OpenBlock::new(engine, Default::default(), false, db3, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b3 = b3.close_and_lock();
engine.set_signer(tap.clone(), addr1, "1".into());
@ -1829,7 +1834,7 @@ mod tests {
engine.register_client(Arc::downgrade(&client) as _);
// step 2
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b1 = OpenBlock::new(engine, Default::default(), false, db1, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b1 = b1.close_and_lock();
// since the block is empty it isn't sealed and we generate empty steps
@ -1839,7 +1844,7 @@ mod tests {
// step 3
// the signer of the accumulated empty step message should be rewarded
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b2 = OpenBlock::new(engine, Default::default(), false, db2, &genesis_header, last_hashes.clone(), addr1, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let addr1_balance = b2.block().state().balance(&addr1).unwrap();
// after closing the block `addr1` should be reward twice, one for the included empty step message and another for block creation
@ -1962,6 +1967,7 @@ mod tests {
(3141562.into(), 31415620.into()),
vec![],
false,
&mut Vec::new().into_iter(),
).unwrap();
let b1 = b1.close_and_lock();
@ -1983,6 +1989,7 @@ mod tests {
(3141562.into(), 31415620.into()),
vec![],
false,
&mut Vec::new().into_iter(),
).unwrap();
let addr1_balance = b2.block().state().balance(&addr1).unwrap();

View File

@ -25,7 +25,7 @@ use block::*;
use engines::{Engine, Seal, ConstructedVerifier, EngineError};
use error::{BlockError, Error};
use ethjson;
use header::Header;
use header::{Header, ExtendedHeader};
use client::EngineClient;
use machine::{AuxiliaryData, Call, EthereumMachine};
use super::signer::EngineSigner;
@ -191,6 +191,10 @@ impl Engine<EthereumMachine> for BasicAuthority {
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
None
}
fn fork_choice(&self, new: &ExtendedHeader, current: &ExtendedHeader) -> super::ForkChoice {
super::total_difficulty_fork_choice(new, current)
}
}
#[cfg(test)]
@ -247,7 +251,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, addr, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b = b.close_and_lock();
if let Seal::Regular(seal) = engine.generate_seal(b.block(), &genesis_header) {
assert!(b.try_seal(engine, seal).is_ok());

View File

@ -15,7 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
use engines::{Engine, Seal};
use parity_machine::{Machine, Transactions};
use parity_machine::{Machine, Transactions, TotalScoredHeader};
/// An engine which does not provide any consensus mechanism, just seals blocks internally.
/// Only seals blocks which have transactions.
@ -33,7 +33,9 @@ impl<M> InstantSeal<M> {
}
impl<M: Machine> Engine<M> for InstantSeal<M>
where M::LiveBlock: Transactions
where M::LiveBlock: Transactions,
M::ExtendedHeader: TotalScoredHeader,
<M::ExtendedHeader as TotalScoredHeader>::Value: Ord
{
fn name(&self) -> &str {
"InstantSeal"
@ -61,6 +63,10 @@ impl<M: Machine> Engine<M> for InstantSeal<M>
fn is_timestamp_valid(&self, header_timestamp: u64, parent_timestamp: u64) -> bool {
header_timestamp >= parent_timestamp
}
fn fork_choice(&self, new: &M::ExtendedHeader, current: &M::ExtendedHeader) -> super::ForkChoice {
super::total_difficulty_fork_choice(new, current)
}
}
#[cfg(test)]
@ -80,7 +86,7 @@ mod tests {
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let genesis_header = spec.genesis_header();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b = b.close_and_lock();
if let Seal::Regular(seal) = engine.generate_seal(b.block(), &genesis_header) {
assert!(b.try_seal(engine, seal).is_ok());

View File

@ -52,15 +52,25 @@ use spec::CommonParams;
use transaction::{self, UnverifiedTransaction, SignedTransaction};
use ethkey::Signature;
use parity_machine::{Machine, LocalizedMachine as Localized};
use parity_machine::{Machine, LocalizedMachine as Localized, TotalScoredHeader};
use ethereum_types::{H256, U256, Address};
use unexpected::{Mismatch, OutOfBounds};
use bytes::Bytes;
use types::ancestry_action::AncestryAction;
/// Default EIP-210 contract code.
/// As defined in https://github.com/ethereum/EIPs/pull/210
pub const DEFAULT_BLOCKHASH_CONTRACT: &'static str = "73fffffffffffffffffffffffffffffffffffffffe33141561006a5760014303600035610100820755610100810715156100455760003561010061010083050761010001555b6201000081071515610064576000356101006201000083050761020001555b5061013e565b4360003512151561008457600060405260206040f361013d565b61010060003543031315156100a857610100600035075460605260206060f361013c565b6101006000350715156100c55762010000600035430313156100c8565b60005b156100ea576101006101006000350507610100015460805260206080f361013b565b620100006000350715156101095763010000006000354303131561010c565b60005b1561012f57610100620100006000350507610200015460a052602060a0f361013a565b600060c052602060c0f35b5b5b5b5b";
/// Fork choice.
#[derive(Debug, PartialEq, Eq)]
pub enum ForkChoice {
/// Choose the new block.
New,
/// Choose the current best block.
Old,
}
/// Voting errors.
#[derive(Debug)]
pub enum EngineError {
@ -208,6 +218,7 @@ pub trait Engine<M: Machine>: Sync + Send {
&self,
_block: &mut M::LiveBlock,
_epoch_begin: bool,
_ancestry: &mut Iterator<Item=M::ExtendedHeader>,
) -> Result<(), M::Error> {
Ok(())
}
@ -348,6 +359,24 @@ pub trait Engine<M: Machine>: Sync + Send {
fn is_timestamp_valid(&self, header_timestamp: u64, parent_timestamp: u64) -> bool {
header_timestamp > parent_timestamp
}
/// Gather all ancestry actions. Called at the last stage when a block is committed. The Engine must guarantee that
/// the ancestry exists.
fn ancestry_actions(&self, _block: &M::LiveBlock, _ancestry: &mut Iterator<Item=M::ExtendedHeader>) -> Vec<AncestryAction> {
Vec::new()
}
/// Check whether the given new block is the best block, after finalization check.
fn fork_choice(&self, new: &M::ExtendedHeader, best: &M::ExtendedHeader) -> ForkChoice;
}
/// Check whether a given block is the best block based on the default total difficulty rule.
pub fn total_difficulty_fork_choice<T: TotalScoredHeader>(new: &T, best: &T) -> ForkChoice where <T as TotalScoredHeader>::Value: Ord {
if new.total_score() > best.total_score() {
ForkChoice::New
} else {
ForkChoice::Old
}
}
/// Common type alias for an engine coupled with an Ethereum-like state machine.

View File

@ -19,7 +19,7 @@ use engines::Engine;
use engines::block_reward::{self, RewardKind};
use header::BlockNumber;
use machine::WithRewards;
use parity_machine::{Header, LiveBlock, WithBalances};
use parity_machine::{Header, LiveBlock, WithBalances, TotalScoredHeader};
/// Params for a null engine.
#[derive(Clone, Default)]
@ -58,7 +58,10 @@ impl<M: Default> Default for NullEngine<M> {
}
}
impl<M: WithBalances + WithRewards> Engine<M> for NullEngine<M> {
impl<M: WithBalances + WithRewards> Engine<M> for NullEngine<M>
where M::ExtendedHeader: TotalScoredHeader,
<M::ExtendedHeader as TotalScoredHeader>::Value: Ord
{
fn name(&self) -> &str {
"NullEngine"
}
@ -101,4 +104,8 @@ impl<M: WithBalances + WithRewards> Engine<M> for NullEngine<M> {
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
Some(Box::new(::snapshot::PowSnapshot::new(10000, 10000)))
}
fn fork_choice(&self, new: &M::ExtendedHeader, current: &M::ExtendedHeader) -> super::ForkChoice {
super::total_difficulty_fork_choice(new, current)
}
}

View File

@ -35,7 +35,7 @@ use unexpected::{OutOfBounds, Mismatch};
use client::EngineClient;
use bytes::Bytes;
use error::{Error, BlockError};
use header::{Header, BlockNumber};
use header::{Header, BlockNumber, ExtendedHeader};
use rlp::Rlp;
use ethkey::{self, Message, Signature};
use account_provider::AccountProvider;
@ -530,7 +530,7 @@ impl Engine<EthereumMachine> for Tendermint {
Ok(())
}
fn on_new_block(&self, block: &mut ExecutedBlock, epoch_begin: bool) -> Result<(), Error> {
fn on_new_block(&self, block: &mut ExecutedBlock, epoch_begin: bool, _ancestry: &mut Iterator<Item=ExtendedHeader>) -> Result<(), Error> {
if !epoch_begin { return Ok(()) }
// genesis is never a new block, but might as well check.
@ -765,6 +765,10 @@ impl Engine<EthereumMachine> for Tendermint {
*self.client.write() = Some(client.clone());
self.validators.register_client(client);
}
fn fork_choice(&self, new: &ExtendedHeader, current: &ExtendedHeader) -> super::ForkChoice {
super::total_difficulty_fork_choice(new, current)
}
}
#[cfg(test)]
@ -800,7 +804,7 @@ mod tests {
let db = spec.ensure_db_good(db, &Default::default()).unwrap();
let genesis_header = spec.genesis_header();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = OpenBlock::new(spec.engine.as_ref(), Default::default(), false, db.boxed_clone(), &genesis_header, last_hashes, proposer, (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b = b.close();
if let Seal::Proposal(seal) = spec.engine.generate_seal(b.block(), &genesis_header) {
(b, seal)

View File

@ -25,7 +25,7 @@ use ethereum_types::{H256, H64, U256, Address};
use unexpected::{OutOfBounds, Mismatch};
use block::*;
use error::{BlockError, Error};
use header::{Header, BlockNumber};
use header::{Header, BlockNumber, ExtendedHeader};
use engines::{self, Engine};
use ethjson;
use rlp::Rlp;
@ -222,14 +222,6 @@ impl Engine<EthereumMachine> for Arc<Ethash> {
header.set_difficulty(difficulty);
}
fn on_new_block(
&self,
_block: &mut ExecutedBlock,
_begins_epoch: bool,
) -> Result<(), Error> {
Ok(())
}
/// Apply the block reward on finalisation of the block.
/// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
@ -364,6 +356,10 @@ impl Engine<EthereumMachine> for Arc<Ethash> {
fn snapshot_components(&self) -> Option<Box<::snapshot::SnapshotComponents>> {
Some(Box::new(::snapshot::PowSnapshot::new(SNAPSHOT_BLOCKS, MAX_SNAPSHOT_BLOCKS)))
}
fn fork_choice(&self, new: &ExtendedHeader, current: &ExtendedHeader) -> engines::ForkChoice {
engines::total_difficulty_fork_choice(new, current)
}
}
impl Ethash {
@ -538,7 +534,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b = b.close();
assert_eq!(b.state().balance(&Address::zero()).unwrap(), U256::from_str("4563918244f40000").unwrap());
}
@ -587,7 +583,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let mut b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let mut b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let mut uncle = Header::new();
let uncle_author: Address = "ef2d6d194084c2de36e0dabfce45d046b37d1106".into();
uncle.set_author(uncle_author);
@ -605,7 +601,7 @@ mod tests {
let genesis_header = spec.genesis_header();
let db = spec.ensure_db_good(get_temp_state_db(), &Default::default()).unwrap();
let last_hashes = Arc::new(vec![genesis_header.hash()]);
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false).unwrap();
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::zero(), (3141562.into(), 31415620.into()), vec![], false, &mut Vec::new().into_iter()).unwrap();
let b = b.close();
let ubi_contract: Address = "00efdd5883ec628983e9063c7d969fe268bbf310".into();

View File

@ -34,6 +34,19 @@ enum Seal {
Without,
}
/// Extended block header, wrapping `Header` with finalized and total difficulty information.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtendedHeader {
/// The actual header.
pub header: Header,
/// Whether the block underlying this header is considered finalized.
pub is_finalized: bool,
/// The parent block difficulty.
pub parent_total_difficulty: U256,
/// The block metadata information.
pub metadata: Option<Vec<u8>>,
}
/// A block header.
///
/// Reflects the specific RLP fields of a block in the chain with additional room for the seal
@ -368,21 +381,48 @@ impl HeapSizeOf for Header {
impl ::parity_machine::Header for Header {
fn bare_hash(&self) -> H256 { Header::bare_hash(self) }
fn hash(&self) -> H256 { Header::hash(self) }
fn seal(&self) -> &[Vec<u8>] { Header::seal(self) }
fn author(&self) -> &Address { Header::author(self) }
fn number(&self) -> BlockNumber { Header::number(self) }
}
impl ::parity_machine::ScoredHeader for Header {
type Value = U256;
fn score(&self) -> &U256 { self.difficulty() }
fn set_score(&mut self, score: U256) { self.set_difficulty(score) }
}
impl ::parity_machine::Header for ExtendedHeader {
fn bare_hash(&self) -> H256 { self.header.bare_hash() }
fn hash(&self) -> H256 { self.header.hash() }
fn seal(&self) -> &[Vec<u8>] { self.header.seal() }
fn author(&self) -> &Address { self.header.author() }
fn number(&self) -> BlockNumber { self.header.number() }
}
impl ::parity_machine::ScoredHeader for ExtendedHeader {
type Value = U256;
fn score(&self) -> &U256 { self.header.difficulty() }
fn set_score(&mut self, score: U256) { self.header.set_difficulty(score) }
}
impl ::parity_machine::TotalScoredHeader for ExtendedHeader {
type Value = U256;
fn total_score(&self) -> U256 { self.parent_total_difficulty + *self.header.difficulty() }
}
impl ::parity_machine::FinalizableHeader for ExtendedHeader {
fn is_finalized(&self) -> bool { self.is_finalized }
}
impl ::parity_machine::WithMetadataHeader for ExtendedHeader {
fn metadata(&self) -> Option<&[u8]> { self.metadata.as_ref().map(|v| v.as_ref()) }
}
#[cfg(test)]
mod tests {
use rustc_hex::FromHex;

View File

@ -25,7 +25,7 @@ use builtin::Builtin;
use client::{BlockInfo, CallContract};
use error::Error;
use executive::Executive;
use header::{BlockNumber, Header};
use header::{BlockNumber, Header, ExtendedHeader};
use spec::CommonParams;
use state::{CleanupMode, Substate};
use trace::{NoopTracer, NoopVMTracer, Tracer, ExecutiveTracer, RewardType, Tracing};
@ -422,10 +422,12 @@ pub enum AuxiliaryRequest {
impl ::parity_machine::Machine for EthereumMachine {
type Header = Header;
type ExtendedHeader = ExtendedHeader;
type LiveBlock = ExecutedBlock;
type EngineClient = ::client::EngineClient;
type AuxiliaryRequest = AuxiliaryRequest;
type AncestryAction = ::types::ancestry_action::AncestryAction;
type Error = Error;
}

View File

@ -22,7 +22,7 @@ use tempdir::TempDir;
use error::{Error, ErrorKind};
use blockchain::generator::{BlockGenerator, BlockBuilder};
use blockchain::BlockChain;
use blockchain::{BlockChain, ExtrasInsert};
use snapshot::{chunk_secondary, Error as SnapshotError, Progress, SnapshotComponents};
use snapshot::io::{PackedReader, PackedWriter, SnapshotReader, SnapshotWriter};
@ -49,7 +49,11 @@ fn chunk_and_restore(amount: u64) {
// build the blockchain.
let mut batch = DBTransaction::new();
for block in generator {
bc.insert_block(&mut batch, &block.encoded(), vec![]);
bc.insert_block(&mut batch, &block.encoded(), vec![], ExtrasInsert {
fork_choice: ::engines::ForkChoice::New,
is_finalized: false,
metadata: None,
});
bc.commit();
}

View File

@ -19,7 +19,7 @@
use account_provider::AccountProvider;
use ethereum_types::{H256, U256, Address};
use block::{OpenBlock, Drain};
use blockchain::{BlockChain, Config as BlockChainConfig};
use blockchain::{BlockChain, Config as BlockChainConfig, ExtrasInsert};
use bytes::Bytes;
use client::{Client, ClientConfig, ChainInfo, ImportBlock, ChainNotify, ChainMessageType, PrepareOpenBlock};
use ethkey::KeyPair;
@ -148,6 +148,7 @@ pub fn generate_dummy_client_with_spec_accounts_and_data<F>(test_spec: F, accoun
(3141562.into(), 31415620.into()),
vec![],
false,
&mut Vec::new().into_iter(),
).unwrap();
rolling_timestamp += 10;
b.set_timestamp(rolling_timestamp);
@ -265,7 +266,12 @@ pub fn generate_dummy_blockchain(block_number: u32) -> BlockChain {
let mut batch = db.transaction();
for block_order in 1..block_number {
bc.insert_block(&mut batch, &create_unverifiable_block(block_order, bc.best_block_hash()), vec![]);
// Total difficulty is always 0 here.
bc.insert_block(&mut batch, &create_unverifiable_block(block_order, bc.best_block_hash()), vec![], ExtrasInsert {
fork_choice: ::engines::ForkChoice::New,
is_finalized: false,
metadata: None,
});
bc.commit();
}
db.write(batch).unwrap();
@ -280,7 +286,12 @@ pub fn generate_dummy_blockchain_with_extra(block_number: u32) -> BlockChain {
let mut batch = db.transaction();
for block_order in 1..block_number {
bc.insert_block(&mut batch, &create_unverifiable_block_with_extra(block_order, bc.best_block_hash(), None), vec![]);
// Total difficulty is always 0 here.
bc.insert_block(&mut batch, &create_unverifiable_block_with_extra(block_order, bc.best_block_hash(), None), vec![], ExtrasInsert {
fork_choice: ::engines::ForkChoice::New,
is_finalized: false,
metadata: None,
});
bc.commit();
}
db.write(batch).unwrap();

View File

@ -87,6 +87,7 @@ fn can_trace_block_and_uncle_reward() {
(3141562.into(), 31415620.into()),
vec![],
false,
&mut Vec::new().into_iter(),
).unwrap();
rolling_timestamp += 10;
root_block.set_timestamp(rolling_timestamp);
@ -115,6 +116,7 @@ fn can_trace_block_and_uncle_reward() {
(3141562.into(), 31415620.into()),
vec![],
false,
&mut Vec::new().into_iter(),
).unwrap();
rolling_timestamp += 10;
parent_block.set_timestamp(rolling_timestamp);
@ -141,7 +143,8 @@ fn can_trace_block_and_uncle_reward() {
author.clone(),
(3141562.into(), 31415620.into()),
vec![],
false
false,
&mut Vec::new().into_iter(),
).unwrap();
rolling_timestamp += 10;
block.set_timestamp(rolling_timestamp);

View File

@ -455,6 +455,8 @@ mod tests {
total_difficulty: header.difficulty().clone(),
parent: header.parent_hash().clone(),
children: Vec::new(),
is_finalized: false,
metadata: None,
}
})
}

View File

@ -0,0 +1,27 @@
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Actions on ancestry blocks when working on a new block.
use ethereum_types::H256;
#[derive(Debug, PartialEq, Eq, Clone)]
/// Actions on a live block's parent block. Only committed when the live block is committed. Those actions here must
/// respect the normal blockchain reorganization rules.
pub enum AncestryAction {
/// Mark an ancestry block as finalized.
MarkFinalized(H256),
}

View File

@ -46,6 +46,7 @@ pub mod state_diff;
pub mod trace_filter;
pub mod tree_route;
pub mod verification_queue_info;
pub mod ancestry_action;
/// Type for block number.
pub type BlockNumber = u64;

View File

@ -27,4 +27,6 @@ pub struct TreeRoute {
pub ancestor: H256,
/// An index where best common ancestor would be.
pub index: usize,
/// Whether it has finalized blocks from `from` (inclusive) to `ancestor` (exclusive).
pub is_from_route_finalized: bool,
}

View File

@ -40,13 +40,35 @@ pub trait Header {
fn number(&self) -> u64;
}
/// a header with an associated score (difficulty in PoW terms)
/// A header with an associated score (difficulty in PoW terms)
pub trait ScoredHeader: Header {
type Value;
/// Get the score of this header.
fn score(&self) -> &U256;
fn score(&self) -> &Self::Value;
/// Set the score of this header.
fn set_score(&mut self, score: U256);
fn set_score(&mut self, score: Self::Value);
}
/// A header with associated total score.
pub trait TotalScoredHeader: Header {
type Value;
/// Get the total score of this header.
fn total_score(&self) -> Self::Value;
}
/// A header with finalized information.
pub trait FinalizableHeader: Header {
/// Get whether this header is considered finalized, so that it will never be replaced in reorganization.
fn is_finalized(&self) -> bool;
}
/// A header with metadata information.
pub trait WithMetadataHeader: Header {
/// Get the current header metadata.
fn metadata(&self) -> Option<&[u8]>;
}
/// A "live" block is one which is in the process of the transition.
@ -73,16 +95,36 @@ pub trait Transactions: LiveBlock {
fn transactions(&self) -> &[Self::Transaction];
}
/// Trait for blocks which have finalized information.
pub trait Finalizable: LiveBlock {
/// Get whether the block is finalized.
fn is_finalized(&self) -> bool;
/// Mark the block as finalized.
fn mark_finalized(&mut self);
}
/// A state machine with block metadata.
pub trait WithMetadata: LiveBlock {
/// Get the current live block metadata.
fn metadata(&self) -> Option<&[u8]>;
/// Set the current live block metadata.
fn set_metadata(&mut self, value: Option<Vec<u8>>);
}
/// Generalization of types surrounding blockchain-suitable state machines.
pub trait Machine: for<'a> LocalizedMachine<'a> {
/// The block header type.
type Header: Header;
/// The live block type.
type LiveBlock: LiveBlock<Header=Self::Header>;
/// Block header with metadata information.
type ExtendedHeader: Header;
/// A handle to a blockchain client for this machine.
type EngineClient: ?Sized;
/// A description of needed auxiliary data.
type AuxiliaryRequest;
/// Actions taken on ancestry blocks when commiting a new block.
type AncestryAction;
/// Errors which can occur when querying or interacting with the machine.
type Error;