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

Commit

Permalink
Merge pull request #119 from gavofyork/client
Browse files Browse the repository at this point in the history
Client state syncing
  • Loading branch information
Gav Wood committed Jan 15, 2016
2 parents 3249228 + bda0470 commit a11f736
Show file tree
Hide file tree
Showing 23 changed files with 400 additions and 135 deletions.
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

0 comments on commit a11f736

Please sign in to comment.