Skip to content
This repository was archived by the owner on Nov 6, 2020. It is now read-only.

Client state syncing #119

Merged
merged 16 commits into from
Jan 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions res/ethereum/frontier.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "Frontier",
"engineName": "Ethash",
"params": {
"accountStartNonce": "0x00",
Expand Down
3 changes: 2 additions & 1 deletion res/ethereum/frontier_test.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"engineName": "Frontier (Test)",
"engineName": "Ethash",
"params": {
"accountStartNonce": "0x00",
Expand Down Expand Up @@ -30,4 +31,4 @@
"0000000000000000000000000000000000000003": { "builtin": { "name": "ripemd160", "linear": { "base": 600, "word": 120 } } },
"0000000000000000000000000000000000000004": { "builtin": { "name": "identity", "linear": { "base": 15, "word": 3 } } }
}
}
}
1 change: 1 addition & 0 deletions res/ethereum/homestead_test.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "Homestead (Test)",
"engineName": "Ethash",
"params": {
"accountStartNonce": "0x00",
Expand Down
3 changes: 2 additions & 1 deletion res/ethereum/morden.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "Morden",
"engineName": "Ethash",
"params": {
"accountStartNonce": "0x0100000",
Expand Down Expand Up @@ -31,4 +32,4 @@
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "linear": { "base": 15, "word": 3 } } },
"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" }
}
}
}
3 changes: 2 additions & 1 deletion res/ethereum/olympic.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "Olympic",
"engineName": "Ethash",
"params": {
"accountStartNonce": "0x00",
Expand Down Expand Up @@ -38,4 +39,4 @@
"6c386a4b26f73c802f34673f7248bb118f97424a": { "balance": "1606938044258990275541962092341162602522202993782792835301376" },
"e4157b34ea9615cfbde6b4fda419828124b70c78": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
}
}
}
3 changes: 2 additions & 1 deletion res/null_morden.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "Morden",
"engineName": "NullEngine",
"params": {
"accountStartNonce": "0x0100000",
Expand Down Expand Up @@ -31,4 +32,4 @@
"0000000000000000000000000000000000000004": { "balance": "1", "nonce": "1048576", "builtin": { "name": "identity", "linear": { "base": 15, "word": 3 } } },
"102e61f5d8f9bc71d0ad4a084df4e65e05ce0e1c": { "balance": "1606938044258990275541962092341162602522202993782792835301376", "nonce": "1048576" }
}
}
}
28 changes: 17 additions & 11 deletions src/bin/client.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
extern crate ethcore_util as util;
extern crate ethcore;
extern crate rustc_serialize;
extern crate log;
extern crate env_logger;

use std::io::*;
use std::env;
use std::sync::Arc;
use log::{LogLevelFilter};
use env_logger::LogBuilder;
use util::hash::*;
use util::network::{NetworkService};
use ethcore::client::Client;
use ethcore::sync::EthSync;
use ethcore::service::ClientService;
use ethcore::ethereum;

fn setup_log() {
let mut builder = LogBuilder::new();
builder.filter(None, LogLevelFilter::Info);

if env::var("RUST_LOG").is_ok() {
builder.parse(&env::var("RUST_LOG").unwrap());
}

builder.init().unwrap();
}

fn main() {
::env_logger::init().ok();
let mut service = NetworkService::start().unwrap();
//TODO: replace with proper genesis and chain params.
setup_log();
let spec = ethereum::new_frontier();
let mut dir = env::temp_dir();
dir.push(H32::random().hex());
let client = Arc::new(Client::new(spec, &dir).unwrap());
EthSync::register(&mut service, client);
let mut _service = ClientService::start(spec).unwrap();
loop {
let mut cmd = String::new();
stdin().read_line(&mut cmd).unwrap();
Expand Down
16 changes: 12 additions & 4 deletions src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ impl<'x, 'y> ClosedBlock<'x, 'y> {

/// Turn this back into an `OpenBlock`.
pub fn reopen(self) -> OpenBlock<'x, 'y> { self.open_block }

/// Drop this object and return the underlieing database.
pub fn drain(self) -> OverlayDB { self.open_block.block.state.drop().1 }
}

impl SealedBlock {
Expand All @@ -251,15 +254,20 @@ impl IsBlock for SealedBlock {
}

/// Enact the block given by `block_bytes` using `engine` on the database `db` with given `parent` block header
///
pub fn enact(block_bytes: &[u8], engine: &Engine, db: OverlayDB, parent: &Header, last_hashes: &LastHashes) -> Result<SealedBlock, Error> {
pub fn enact<'x, 'y>(block_bytes: &[u8], engine: &'x Engine, db: OverlayDB, parent: &Header, last_hashes: &'y LastHashes) -> Result<ClosedBlock<'x, 'y>, Error> {
let block = BlockView::new(block_bytes);
let header = block.header_view();
let mut b = OpenBlock::new(engine, db, parent, last_hashes, header.author(), header.extra_data());
b.set_timestamp(header.timestamp());
for t in block.transactions().into_iter() { try!(b.push_transaction(t, None)); }
for u in block.uncles().into_iter() { try!(b.push_uncle(u)); }
Ok(try!(b.close().seal(header.seal())))
Ok(b.close())
}

/// Enact the block given by `block_bytes` using `engine` on the database `db` with given `parent` block header. Seal the block aferwards
pub fn enact_and_seal(block_bytes: &[u8], engine: &Engine, db: OverlayDB, parent: &Header, last_hashes: &LastHashes) -> Result<SealedBlock, Error> {
let header = BlockView::new(block_bytes).header_view();
Ok(try!(try!(enact(block_bytes, engine, db, parent, last_hashes)).seal(header.seal())))
}

#[test]
Expand Down Expand Up @@ -289,7 +297,7 @@ fn enact_block() {

let mut db = OverlayDB::new_temp();
engine.spec().ensure_db_good(&mut db);
let e = enact(&orig_bytes, engine.deref(), db, &genesis_header, &vec![genesis_header.hash()]).unwrap();
let e = enact_and_seal(&orig_bytes, engine.deref(), db, &genesis_header, &vec![genesis_header.hash()]).unwrap();

assert_eq!(e.rlp_bytes(), orig_bytes);

Expand Down
85 changes: 80 additions & 5 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use util::*;
use rocksdb::{DB};
use blockchain::{BlockChain, BlockProvider};
use views::BlockView;
use error::*;
use header::BlockNumber;
use spec::Spec;
use engine::Engine;
use queue::BlockQueue;
use sync::NetSyncMessage;
use env_info::LastHashes;
use verification::*;
use block::*;

/// General block status
pub enum BlockStatus {
Expand Down Expand Up @@ -41,7 +46,7 @@ pub struct BlockQueueStatus {
pub type TreeRoute = ::blockchain::TreeRoute;

/// Blockchain database client. Owns and manages a blockchain and a block queue.
pub trait BlockChainClient : Sync {
pub trait BlockChainClient : Sync + Send {
/// Get raw block header data by block header hash.
fn block_header(&self, hash: &H256) -> Option<Bytes>;

Expand Down Expand Up @@ -94,20 +99,86 @@ pub trait BlockChainClient : Sync {
/// Blockchain database client backed by a persistent database. Owns and manages a blockchain and a block queue.
pub struct Client {
chain: Arc<RwLock<BlockChain>>,
_engine: Arc<Box<Engine>>,
engine: Arc<Box<Engine>>,
state_db: OverlayDB,
queue: BlockQueue,
}

impl Client {
pub fn new(spec: Spec, path: &Path) -> Result<Client, Error> {
/// Create a new client with given spec and DB path.
pub fn new(spec: Spec, path: &Path, message_channel: IoChannel<NetSyncMessage> ) -> Result<Client, Error> {
let chain = Arc::new(RwLock::new(BlockChain::new(&spec.genesis_block(), path)));
let engine = Arc::new(try!(spec.to_engine()));
let mut state_path = path.to_path_buf();
state_path.push("state");
let db = DB::open_default(state_path.to_str().unwrap()).unwrap();
let mut state_db = OverlayDB::new(db);
engine.spec().ensure_db_good(&mut state_db);
state_db.commit().expect("Error commiting genesis state to state DB");

Ok(Client {
chain: chain.clone(),
_engine: engine.clone(),
queue: BlockQueue::new(chain.clone(), engine.clone()),
engine: engine.clone(),
state_db: state_db,
queue: BlockQueue::new(engine.clone(), message_channel),
})
}

/// This is triggered by a message coming from a block queue when the block is ready for insertion
pub fn import_verified_block(&mut self, bytes: Bytes) {
let block = BlockView::new(&bytes);
let header = block.header();
if let Err(e) = verify_block_family(&header, &bytes, self.engine.deref().deref(), self.chain.read().unwrap().deref()) {
warn!(target: "client", "Stage 3 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
self.queue.mark_as_bad(&header.hash());
return;
};
let parent = match self.chain.read().unwrap().block_header(&header.parent_hash) {
Some(p) => p,
None => {
warn!(target: "client", "Block import failed for #{} ({}): Parent not found ({}) ", header.number(), header.hash(), header.parent_hash);
self.queue.mark_as_bad(&header.hash());
return;
},
};
// build last hashes
let mut last = self.chain.read().unwrap().best_block_hash();
let mut last_hashes = LastHashes::new();
last_hashes.resize(256, H256::new());
for i in 0..255 {
match self.chain.read().unwrap().block_details(&last) {
Some(details) => {
last_hashes[i + 1] = details.parent.clone();
last = details.parent.clone();
},
None => break,
}
}

let result = match enact(&bytes, self.engine.deref().deref(), self.state_db.clone(), &parent, &last_hashes) {
Ok(b) => b,
Err(e) => {
warn!(target: "client", "Block import failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
self.queue.mark_as_bad(&header.hash());
return;
}
};
if let Err(e) = verify_block_final(&header, result.block().header()) {
warn!(target: "client", "Stage 4 block verification failed for #{} ({})\nError: {:?}", header.number(), header.hash(), e);
self.queue.mark_as_bad(&header.hash());
return;
}

self.chain.write().unwrap().insert_block(&bytes); //TODO: err here?
match result.drain().commit() {
Ok(_) => (),
Err(e) => {
warn!(target: "client", "State DB commit failed: {:?}", e);
return;
}
}
info!(target: "client", "Imported #{} ({})", header.number(), header.hash());
}
}

impl BlockChainClient for Client {
Expand Down Expand Up @@ -165,6 +236,10 @@ impl BlockChainClient for Client {
}

fn import_block(&mut self, bytes: &[u8]) -> ImportResult {
let header = BlockView::new(bytes).header();
if self.chain.read().unwrap().is_known(&header.hash()) {
return Err(ImportError::AlreadyInChain);
}
self.queue.import_block(bytes)
}

Expand Down
2 changes: 1 addition & 1 deletion src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub trait Engine : Sync + Send {

/// Phase 3 verification. Check block information against parent and uncles. `block` (the header's full block)
/// may be provided for additional checks. Returns either a null `Ok` or a general error detailing the problem with import.
fn verify_block_final(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }
fn verify_block_family(&self, _header: &Header, _parent: &Header, _block: Option<&[u8]>) -> Result<(), Error> { Ok(()) }

/// Additional verification for transactions in blocks.
// TODO: Add flags for which bits of the transaction to check.
Expand Down
27 changes: 20 additions & 7 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use util::*;
use header::BlockNumber;
use basic_types::LogBloom;

#[derive(Debug, PartialEq, Eq)]
pub struct Mismatch<T: fmt::Debug> {
Expand Down Expand Up @@ -53,15 +54,15 @@ pub enum BlockError {
UncleIsBrother(OutOfBounds<BlockNumber>),
UncleInChain(H256),
UncleParentNotInChain(H256),
InvalidStateRoot,
InvalidGasUsed,
InvalidStateRoot(Mismatch<H256>),
InvalidGasUsed(Mismatch<U256>),
InvalidTransactionsRoot(Mismatch<H256>),
InvalidDifficulty(Mismatch<U256>),
InvalidGasLimit(OutOfBounds<U256>),
InvalidReceiptsStateRoot,
InvalidReceiptsStateRoot(Mismatch<H256>),
InvalidTimestamp(OutOfBounds<u64>),
InvalidLogBloom,
InvalidBlockNonce,
InvalidLogBloom(Mismatch<LogBloom>),
InvalidBlockNonce(Mismatch<H256>),
InvalidParentHash(Mismatch<H256>),
InvalidNumber(OutOfBounds<BlockNumber>),
UnknownParent(H256),
Expand All @@ -70,14 +71,14 @@ pub enum BlockError {

#[derive(Debug)]
pub enum ImportError {
Bad(Error),
Bad(Option<Error>),
AlreadyInChain,
AlreadyQueued,
}

impl From<Error> for ImportError {
fn from(err: Error) -> ImportError {
ImportError::Bad(err)
ImportError::Bad(Some(err))
}
}

Expand Down Expand Up @@ -124,6 +125,18 @@ impl From<DecoderError> for Error {
}
}

impl From<UtilError> for Error {
fn from(err: UtilError) -> Error {
Error::Util(err)
}
}

impl From<IoError> for Error {
fn from(err: IoError) -> Error {
Error::Util(From::from(err))
}
}

// TODO: uncomment below once https://github.com/rust-lang/rust/issues/27336 sorted.
/*#![feature(concat_idents)]
macro_rules! assimilate {
Expand Down
24 changes: 22 additions & 2 deletions src/ethereum/ethash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ impl Engine for Ethash {
// Bestow uncle rewards
let current_number = fields.header.number();
for u in fields.uncles.iter() {
fields.state.add_balance(u.author(), &(reward * U256::from((8 + u.number() - current_number) / 8)));
fields.state.add_balance(u.author(), &(reward * U256::from(8 + u.number() - current_number) / U256::from(8)));
}
fields.state.commit();
}

fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
Expand All @@ -75,7 +76,7 @@ impl Engine for Ethash {
Ok(())
}

fn verify_block_final(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
fn verify_block_family(&self, header: &Header, parent: &Header, _block: Option<&[u8]>) -> result::Result<(), Error> {
// Check difficulty is correct given the two timestamps.
let expected_difficulty = self.calculate_difficuty(header, parent);
if header.difficulty != expected_difficulty {
Expand Down Expand Up @@ -143,4 +144,23 @@ fn on_close_block() {
assert_eq!(b.state().balance(&Address::zero()), U256::from_str("4563918244f40000").unwrap());
}

#[test]
fn on_close_block_with_uncle() {
use super::*;
let engine = new_morden().to_engine().unwrap();
let genesis_header = engine.spec().genesis_header();
let mut db = OverlayDB::new_temp();
engine.spec().ensure_db_good(&mut db);
let last_hashes = vec![genesis_header.hash()];
let mut b = OpenBlock::new(engine.deref(), db, &genesis_header, &last_hashes, Address::zero(), vec![]);
let mut uncle = Header::new();
let uncle_author = address_from_hex("ef2d6d194084c2de36e0dabfce45d046b37d1106");
uncle.author = uncle_author.clone();
b.push_uncle(uncle).unwrap();

let b = b.close();
assert_eq!(b.state().balance(&Address::zero()), U256::from_str("478eae0e571ba000").unwrap());
assert_eq!(b.state().balance(&uncle_author), U256::from_str("3cb71f51fc558000").unwrap());
}

// TODO: difficulty test
Loading