diff --git a/.gitignore b/.gitignore index 35bf9d4..d5f1ae6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target *.iml Cargo.lock +client.db.* diff --git a/Cargo.toml b/Cargo.toml index ff48340..5fd02c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,17 @@ keywords = [ "bitcoin" ] readme = "README.md" edition = "2018" +[features] +default = ["hammersbald"] + [lib] name = "murmel" path = "src/lib.rs" [dependencies] bitcoin = "0.26" -hammersbald = { version = "3.0.1", features = [ "bitcoin_support" ] } +lightning = { version ="0.0.9", optional=true } +bitcoin_hashes = "0.7" mio = "0.6" rand = "0.7" log = "0.4" @@ -29,6 +33,9 @@ futures-timer = "0.3" serde="1" serde_derive="1" +## optional +hammersbald = { version = "3.0.1", features = [ "bitcoin_support" ], optional=true } + [dev-dependencies] rustc-serialize = "0.3" hex = "0.3" diff --git a/src/chaindb.rs b/src/chaindb.rs index 9a6e76c..74264eb 100644 --- a/src/chaindb.rs +++ b/src/chaindb.rs @@ -14,177 +14,71 @@ // limitations under the License. // //! -//! # Blockchain DB for a node +//! # Blockchain DB API for a node //! use std::io; use std::sync::{Arc, RwLock}; -use std::path::Path; -use bitcoin::Network; use bitcoin::blockdata::block::BlockHeader; use bitcoin::consensus::encode::{Decodable, Encodable}; -use bitcoin::{BlockHash, blockdata::constants::genesis_block}; +use bitcoin::BlockHash; -use hammersbald::{BitcoinAdaptor, BitcoinObject, HammersbaldAPI, persistent, transient}; +use hammersbald::BitcoinObject; use crate::error::Error; -use crate::headercache::{CachedHeader, HeaderCache}; -use log::{debug, info, warn, error}; +use crate::headercache::CachedHeader; + use serde_derive::{Serialize, Deserialize}; /// Shared handle to a database storing the block chain /// protected by an RwLock -pub type SharedChainDB = Arc>; - -/// Database storing the block chain -pub struct ChainDB { - db: BitcoinAdaptor, - headercache: HeaderCache, - network: Network, -} - -impl ChainDB { - /// Create an in-memory database instance - pub fn mem(network: Network) -> Result { - info!("working with in memory chain db"); - let db = BitcoinAdaptor::new(transient(2)?); - let headercache = HeaderCache::new(network); - Ok(ChainDB { db, network, headercache }) - } +pub type SharedChainDB = Arc>>; - /// Create or open a persistent database instance identified by the path - pub fn new(path: &Path, network: Network) -> Result { - let basename = path.to_str().unwrap().to_string(); - let db = BitcoinAdaptor::new(persistent((basename.clone()).as_str(), 100, 2)?); - let headercache = HeaderCache::new(network); - Ok(ChainDB { db, network, headercache }) - } +/// Blockchain DB API for a client node. +pub trait ChainDB: Send + Sync { - /// Initialize caches - pub fn init(&mut self) -> Result<(), Error> { - self.init_headers()?; - Ok(()) - } + /// Initialize caches. + fn init(&mut self) -> Result<(), Error>; /// Batch updates. Updates are permanent after finishing a batch. - pub fn batch(&mut self) -> Result<(), Error> { - self.db.batch()?; - Ok(()) - } + fn batch(&mut self) -> Result<(), Error>; - fn init_headers(&mut self) -> Result<(), Error> { - if let Some(tip) = self.fetch_header_tip()? { - info!("reading stored header chain from tip {}", tip); - if self.fetch_header(tip)?.is_some() { - let mut h = tip; - while let Some(stored) = self.fetch_header(h)? { - debug!("read stored header {}", &stored.block_hash()); - self.headercache.add_header_unchecked(&h, &stored); - if stored.header.prev_blockhash != BlockHash::default() { - h = stored.header.prev_blockhash; - } else { - break; - } - } - self.headercache.reverse_trunk(); - info!("read {} headers", self.headercache.len()); - } else { - warn!("unable to read header for tip {}", tip); - self.init_to_genesis()?; - } - } else { - info!("no header tip found"); - self.init_to_genesis()?; - } - Ok(()) - } + /// Store a header. + fn add_header(&mut self, header: &BlockHeader) -> Result>, Option>)>, Error>; - fn init_to_genesis(&mut self) -> Result<(), Error> { - let genesis = genesis_block(self.network).header; - if let Some((cached, _, _)) = self.headercache.add_header(&genesis)? { - info!("initialized with genesis header {}", genesis.block_hash()); - self.db.put_object_by_hash(&cached.stored)?; - self.db.batch()?; - self.store_header_tip(&cached.block_hash())?; - self.db.batch()?; - } else { - error!("failed to initialize with genesis header"); - return Err(Error::NoTip); - } - Ok(()) - } + /// Return position of hash on trunk if hash is on trunk. + fn pos_on_trunk(&self, hash: &BlockHash) -> Option; - /// Store a header - pub fn add_header(&mut self, header: &BlockHeader) -> Result>, Option>)>, Error> { - if let Some((cached, unwinds, forward)) = self.headercache.add_header(header)? { - self.db.put_object_by_hash::<_, _>(&cached.stored)?; - if let Some(forward) = forward.clone() { - if forward.len() > 0 { - self.store_header_tip(forward.last().unwrap())?; - } - } - return Ok(Some((cached.stored, unwinds, forward))); - } - Ok(None) - } + /// Iterate trunk [from .. tip]. + fn iter_trunk<'a>(&'a self, from: u32) -> Box + 'a>; - /// return position of hash on trunk if hash is on trunk - pub fn pos_on_trunk(&self, hash: &BlockHash) -> Option { - self.headercache.pos_on_trunk(hash) - } + /// Iterate trunk [genesis .. from] in reverse order from is the tip if not specified. + fn iter_trunk_rev<'a>(&'a self, from: Option) -> Box + 'a>; - /// iterate trunk [from .. tip] - pub fn iter_trunk<'a>(&'a self, from: u32) -> impl Iterator + 'a { - self.headercache.iter_trunk(from) - } + /// Retrieve the id of the block/header with most work. + fn header_tip(&self) -> Option; - /// iterate trunk [genesis .. from] in reverse order from is the tip if not specified - pub fn iter_trunk_rev<'a>(&'a self, from: Option) -> impl Iterator + 'a { - self.headercache.iter_trunk_rev(from) - } + /// Fetch a header by its id from cache. + fn get_header(&self, id: &BlockHash) -> Option; - /// retrieve the id of the block/header with most work - pub fn header_tip(&self) -> Option { - self.headercache.tip() - } + /// Fetch a header by its id from cache. + fn get_header_for_height(&self, height: u32) -> Option; - /// Fetch a header by its id from cache - pub fn get_header(&self, id: &BlockHash) -> Option { - self.headercache.get_header(id) - } + /// Locator for getheaders message. + fn header_locators(&self) -> Vec; - /// Fetch a header by its id from cache - pub fn get_header_for_height(&self, height: u32) -> Option { - self.headercache.get_header_for_height(height) - } + /// Store the header id with most work. + fn store_header_tip(&mut self, tip: &BlockHash) -> Result<(), Error>; - /// locator for getheaders message - pub fn header_locators(&self) -> Vec { - self.headercache.locator_hashes() - } + /// Find header id with most work. + fn fetch_header_tip(&self) -> Result, Error>; - /// Store the header id with most work - pub fn store_header_tip(&mut self, tip: &BlockHash) -> Result<(), Error> { - self.db.put_object_by_key(HEADER_TIP_KEY, tip)?; - Ok(()) - } + /// Read header from the DB. + fn fetch_header(&self, id: BlockHash) -> Result, Error>; - /// Find header id with most work - pub fn fetch_header_tip(&self) -> Result, Error> { - Ok(self.db.get_object_by_key::(HEADER_TIP_KEY)?.map(|(_, h)| h.clone())) - } - - /// Read header from the DB - pub fn fetch_header(&self, id: BlockHash) -> Result, Error> { - Ok(self.db.get_object_by_hash(id)?.map(|(_, header)| header)) - } - - /// Shutdown db - pub fn shutdown(&mut self) { - self.db.shutdown(); - debug!("shutdown chain db") - } + /// Shutdown the DB. + fn shutdown(&mut self); } /// A header enriched with information about its position on the blockchain @@ -198,7 +92,6 @@ pub struct StoredHeader { pub log2work: f64, } -// need to implement if put_hash_keyed and get_hash_keyed should be used impl StoredHeader { pub fn block_hash(&self) -> BlockHash { self.header.block_hash() @@ -224,45 +117,3 @@ impl BitcoinObject for StoredHeader { self.block_hash() } } - -const HEADER_TIP_KEY: &[u8] = &[0u8; 1]; - -#[cfg(test)] -mod test { - use bitcoin::Network; - use bitcoin::blockdata::constants::genesis_block; - - use crate::chaindb::ChainDB; - - #[test] - fn init_tip_header() { - let network = Network::Testnet; - let genesis_header = genesis_block(network).header; - - let mut chaindb = ChainDB::mem(network).unwrap(); - chaindb.init().unwrap(); - chaindb.init().unwrap(); - - let header_tip = chaindb.header_tip(); - assert!(header_tip.is_some(), "failed to get header for tip"); - assert!(header_tip.unwrap().stored.block_hash().eq(&genesis_header.block_hash())) - } - - #[test] - fn init_recover_if_missing_tip_header() { - let network = Network::Testnet; - let genesis_header = genesis_block(network).header; - - let mut chaindb = ChainDB::mem(network).unwrap(); - let missing_tip_header_hash = "6cfb35868c4465b7c289d7d5641563aa973db6a929655282a7bf95c8257f53ef".parse().unwrap(); - chaindb.store_header_tip(&missing_tip_header_hash).unwrap(); - - chaindb.init().unwrap(); - - let header_tip = chaindb.header_tip(); - assert!(header_tip.is_some(), "failed to get header for tip"); - assert!(header_tip.unwrap().stored.block_hash().eq(&genesis_header.block_hash())) - } -} - - diff --git a/src/constructor.rs b/src/constructor.rs index 8bbbccb..7f20035 100644 --- a/src/constructor.rs +++ b/src/constructor.rs @@ -24,7 +24,7 @@ use bitcoin::{ constants::{ServiceFlags, Network} } }; -use crate::chaindb::{ChainDB, SharedChainDB}; +use crate::hammersbald::Hammersbald; use crate::dispatcher::Dispatcher; use crate::dns::dns_seed; use crate::error::Error; @@ -54,6 +54,7 @@ use bitcoin::network::message::NetworkMessage; use bitcoin::network::message::RawNetworkMessage; use crate::p2p::BitcoinP2PConfig; use std::time::Duration; +use crate::chaindb::SharedChainDB; const MAX_PROTOCOL_VERSION: u32 = 70001; const USER_AGENT: &'static str = concat!("/Murmel:", env!("CARGO_PKG_VERSION"), '/'); @@ -70,9 +71,11 @@ impl Constructor { pub fn open_db(path: Option<&Path>, network: Network, _birth: u64) -> Result { let mut chaindb = if let Some(path) = path { - ChainDB::new(path, network)? + #[cfg(feature = "default")] + Hammersbald::new(path, network)? } else { - ChainDB::mem(network)? + #[cfg(feature = "default")] + Hammersbald::mem(network)? }; chaindb.init()?; Ok(Arc::new(RwLock::new(chaindb))) diff --git a/src/dns.rs b/src/dns.rs index daa4c24..d28aef3 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -26,24 +26,27 @@ use bitcoin::network::constants::Network; use log::{info, trace}; use std::net::{SocketAddr, ToSocketAddrs}; -const MAIN_SEEDER: [&str;5] = [ +const MAIN_SEEDER: [&str; 9] = [ "seed.bitcoin.sipa.be", "dnsseed.bluematt.me", "dnsseed.bitcoin.dashjr.org", "seed.bitcoinstats.com", - "seed.btc.petertodd.org" + "seed.bitcoin.jonasschnelli.ch", + "seed.btc.petertodd.org", + "seed.bitcoin.sprovoost.nl", + "dnsseed.emzy.de", + "seed.bitcoin.wiz.biz", ]; -const TEST_SEEDER: [&str;4] = [ +const TEST_SEEDER: [&str; 4] = [ "testnet-seed.bitcoin.jonasschnelli.ch", "seed.tbtc.petertodd.org", "seed.testnet.bitcoin.sprovoost.nl", - "testnet-seed.bluematt.me" + "testnet-seed.bluematt.me", ]; - -pub fn dns_seed (network: Network) -> Vec { - let mut seeds = Vec::new (); +pub fn dns_seed(network: Network) -> Vec { + let mut seeds = Vec::new(); if network == Network::Bitcoin { info!("reaching out for DNS seed..."); for seedhost in MAIN_SEEDER.iter() { @@ -71,4 +74,4 @@ pub fn dns_seed (network: Network) -> Vec { info!("received {} DNS seeds", seeds.len()); } seeds -} \ No newline at end of file +} diff --git a/src/hammersbald.rs b/src/hammersbald.rs new file mode 100644 index 0000000..8559773 --- /dev/null +++ b/src/hammersbald.rs @@ -0,0 +1,228 @@ +// +// Copyright 2018-2019 Tamas Blummer +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//! +//! # Blockchain DB for a node +//! + +use std::path::Path; + +use bitcoin::Network; +use bitcoin::blockdata::block::BlockHeader; +use bitcoin::{BlockHash, blockdata::constants::genesis_block}; + +use hammersbald::{BitcoinAdaptor, HammersbaldAPI, persistent, transient}; + +use crate::error::Error; +use crate::headercache::{CachedHeader, HeaderCache}; +use log::{debug, info, warn, error}; +use crate::chaindb::StoredHeader; +use crate::chaindb::ChainDB; + +/// Database storing the block chain +pub struct Hammersbald { + db: BitcoinAdaptor, + headercache: HeaderCache, + network: Network, +} + + +impl Hammersbald { + + /// Create an in-memory database instance + pub fn mem(network: Network) -> Result, Error> { + info!("working with in memory chain db"); + let db = BitcoinAdaptor::new(transient(2)?); + let headercache = HeaderCache::new(network); + Ok(Box::from(Hammersbald { db, network, headercache })) + } + + /// Create or open a persistent database instance identified by the path + pub fn new(path: &Path, network: Network) -> Result, Error> { + let basename = path.to_str().unwrap().to_string(); + let db = BitcoinAdaptor::new(persistent((basename.clone()).as_str(), 100, 2)?); + let headercache = HeaderCache::new(network); + Ok(Box::from(Hammersbald { db, network, headercache })) + } + + fn init_headers(&mut self) -> Result<(), Error> { + if let Some(tip) = self.fetch_header_tip()? { + info!("reading stored header chain from tip {}", tip); + if self.fetch_header(tip)?.is_some() { + let mut h = tip; + while let Some(stored) = self.fetch_header(h)? { + debug!("read stored header {}", &stored.block_hash()); + self.headercache.add_header_unchecked(&h, &stored); + if stored.header.prev_blockhash != BlockHash::default() { + h = stored.header.prev_blockhash; + } else { + break; + } + } + self.headercache.reverse_trunk(); + info!("read {} headers", self.headercache.len()); + } else { + warn!("unable to read header for tip {}", tip); + self.init_to_genesis()?; + } + } else { + info!("no header tip found"); + self.init_to_genesis()?; + } + Ok(()) + } + + fn init_to_genesis(&mut self) -> Result<(), Error> { + let genesis = genesis_block(self.network).header; + if let Some((cached, _, _)) = self.headercache.add_header(&genesis)? { + info!("initialized with genesis header {}", genesis.block_hash()); + self.db.put_object_by_hash(&cached.stored)?; + self.db.batch()?; + self.store_header_tip(&cached.block_hash())?; + self.db.batch()?; + } else { + error!("failed to initialize with genesis header"); + return Err(Error::NoTip); + } + Ok(()) + } +} + +impl ChainDB for Hammersbald { + + /// Initialize caches + fn init(&mut self) -> Result<(), Error> { + self.init_headers()?; + Ok(()) + } + + /// Batch updates. Updates are permanent after finishing a batch. + fn batch(&mut self) -> Result<(), Error> { + self.db.batch()?; + Ok(()) + } + + /// Store a header + fn add_header(&mut self, header: &BlockHeader) -> Result>, Option>)>, Error> { + if let Some((cached, unwinds, forward)) = self.headercache.add_header(header)? { + self.db.put_object_by_hash(&cached.stored)?; + if let Some(forward) = forward.clone() { + if forward.len() > 0 { + self.store_header_tip(forward.last().unwrap())?; + } + } + return Ok(Some((cached.stored, unwinds, forward))); + } + Ok(None) + } + + /// return position of hash on trunk if hash is on trunk + fn pos_on_trunk(&self, hash: &BlockHash) -> Option { + self.headercache.pos_on_trunk(hash) + } + + /// iterate trunk [from .. tip] + fn iter_trunk<'a>(&'a self, from: u32) -> Box +'a> { + self.headercache.iter_trunk(from) + } + + /// iterate trunk [genesis .. from] in reverse order from is the tip if not specified + fn iter_trunk_rev<'a>(&'a self, from: Option) -> Box +'a> { + self.headercache.iter_trunk_rev(from) + } + + /// retrieve the id of the block/header with most work + fn header_tip(&self) -> Option { + self.headercache.tip() + } + + /// Fetch a header by its id from cache + fn get_header(&self, id: &BlockHash) -> Option { + self.headercache.get_header(id) + } + + /// Fetch a header by its id from cache + fn get_header_for_height(&self, height: u32) -> Option { + self.headercache.get_header_for_height(height) + } + + /// locator for getheaders message + fn header_locators(&self) -> Vec { + self.headercache.locator_hashes() + } + + /// Store the header id with most work + fn store_header_tip(&mut self, tip: &BlockHash) -> Result<(), Error> { + self.db.put_object_by_key(HEADER_TIP_KEY, tip)?; + Ok(()) + } + + /// Find header id with most work + fn fetch_header_tip(&self) -> Result, Error> { + Ok(self.db.get_object_by_key::(HEADER_TIP_KEY)?.map(|(_, h)| h.clone())) + } + + /// Read header from the DB + fn fetch_header(&self, id: BlockHash) -> Result, Error> { + Ok(self.db.get_object_by_hash(id)?.map(|(_, header)| header)) + } + + /// Shutdown db + fn shutdown(&mut self) { + self.db.shutdown(); + debug!("shutdown chain db") + } +} + +const HEADER_TIP_KEY: &[u8] = &[0u8; 1]; + +#[cfg(test)] +mod test { + use bitcoin::Network; + use bitcoin::BlockHash; + use bitcoin::blockdata::constants::genesis_block; + + use crate::hammersbald::Hammersbald; + + #[test] + fn init_tip_header() { + let network = Network::Testnet; + let genesis_header = genesis_block(network).header; + + let mut chaindb = Hammersbald::mem(network).unwrap(); + chaindb.init().unwrap(); + chaindb.init().unwrap(); + + let header_tip = chaindb.header_tip(); + assert!(header_tip.is_some(), "failed to get header for tip"); + assert!(header_tip.unwrap().stored.block_hash().eq(&genesis_header.block_hash())) + } + + #[test] + fn init_recover_if_missing_tip_header() { + let network = Network::Testnet; + let genesis_header = genesis_block(network).header; + + let mut chaindb = Hammersbald::mem(network).unwrap(); + let missing_tip_header_hash: BlockHash = "6cfb35868c4465b7c289d7d5641563aa973db6a929655282a7bf95c8257f53ef".parse().unwrap(); + chaindb.store_header_tip(&missing_tip_header_hash).unwrap(); + + chaindb.init().unwrap(); + + let header_tip = chaindb.header_tip(); + assert!(header_tip.is_some(), "failed to get header for tip"); + assert!(header_tip.unwrap().stored.block_hash().eq(&genesis_header.block_hash())) + } +} diff --git a/src/lib.rs b/src/lib.rs index f993737..d393a3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,7 @@ pub mod dispatcher; pub mod p2p; pub mod error; pub mod chaindb; +#[cfg(feature = "default")] pub mod hammersbald; pub mod constructor; pub use error::Error; \ No newline at end of file diff --git a/src/ping.rs b/src/ping.rs index f8835fa..650acc2 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -65,6 +65,9 @@ impl Ping { self.timeout.lock().unwrap().received(pid, 1, ExpectedReply::Pong); } } + NetworkMessage::Ping(nonce) => { + self.p2p.send_network(pid, NetworkMessage::Pong(nonce)); + } _ => { } } }