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 226ac1f..5fd02c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,15 +11,17 @@ keywords = [ "bitcoin" ] readme = "README.md" edition = "2018" +[features] +default = ["hammersbald"] + [lib] name = "murmel" path = "src/lib.rs" [dependencies] +bitcoin = "0.26" lightning = { version ="0.0.9", optional=true } -bitcoin = { version= "0.21", features=["use-serde"]} bitcoin_hashes = "0.7" -hammersbald = { version= "2.4", features=["bitcoin_support"]} mio = "0.6" rand = "0.7" log = "0.4" @@ -31,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/bin/client.rs b/src/bin/client.rs index 4409fa2..50eb471 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -82,6 +82,7 @@ pub fn main() { Network::Bitcoin => 8333, Network::Testnet => 18333, Network::Regtest => 18444, + Network::Signet => 38333, }; peers.push(SocketAddr::from(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port))); } diff --git a/src/chaindb.rs b/src/chaindb.rs index 5173788..74264eb 100644 --- a/src/chaindb.rs +++ b/src/chaindb.rs @@ -14,176 +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::{BitcoinHash, Network}; use bitcoin::blockdata::block::BlockHeader; -use bitcoin::blockdata::constants::genesis_block; +use bitcoin::consensus::encode::{Decodable, Encodable}; +use bitcoin::BlockHash; -use bitcoin_hashes::sha256d; -use hammersbald::{BitcoinAdaptor, 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>; +pub type SharedChainDB = Arc>>; -/// Database storing the block chain -pub struct ChainDB { - db: BitcoinAdaptor, - headercache: HeaderCache, - network: Network, -} +/// Blockchain DB API for a client node. +pub trait ChainDB: Send + Sync { -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 }) - } - - /// 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 }) - } - - /// 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.bitcoin_hash()); - self.headercache.add_header_unchecked(&h, &stored); - if stored.header.prev_blockhash != sha256d::Hash::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.bitcoin_hash()); - self.db.put_hash_keyed(&cached.stored)?; - self.db.batch()?; - self.store_header_tip(&cached.bitcoin_hash())?; - self.db.batch()?; - } else { - error!("failed to initialize with genesis header"); - return Err(Error::NoTip); - } - Ok(()) - } - - /// 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_hash_keyed(&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 - pub fn pos_on_trunk(&self, hash: &sha256d::Hash) -> Option { - self.headercache.pos_on_trunk(hash) - } + /// Return position of hash on trunk if hash is on trunk. + fn pos_on_trunk(&self, hash: &BlockHash) -> Option; - /// iterate trunk [from .. tip] - pub fn iter_trunk<'a>(&'a self, from: u32) -> impl Iterator + 'a { - self.headercache.iter_trunk(from) - } + /// Iterate trunk [from .. tip]. + fn iter_trunk<'a>(&'a self, from: u32) -> Box + 'a>; - /// 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) - } + /// 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>; - /// retrieve the id of the block/header with most work - pub fn header_tip(&self) -> Option { - self.headercache.tip() - } + /// Retrieve the id of the block/header with most work. + fn header_tip(&self) -> Option; - /// Fetch a header by its id from cache - pub fn get_header(&self, id: &sha256d::Hash) -> Option { - self.headercache.get_header(id) - } + /// Fetch a header by its id from cache. + fn get_header(&self, id: &BlockHash) -> Option; - /// 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) - } + /// Fetch a header by its id from cache. + fn get_header_for_height(&self, height: u32) -> Option; - /// locator for getheaders message - pub fn header_locators(&self) -> Vec { - self.headercache.locator_hashes() - } + /// Locator for getheaders message. + fn header_locators(&self) -> Vec; - /// Store the header id with most work - pub fn store_header_tip(&mut self, tip: &sha256d::Hash) -> Result<(), Error> { - self.db.put_keyed_encodable(HEADER_TIP_KEY, tip)?; - Ok(()) - } + /// Store the header id with most work. + fn store_header_tip(&mut self, tip: &BlockHash) -> Result<(), Error>; - /// Find header id with most work - pub fn fetch_header_tip(&self) -> Result, Error> { - Ok(self.db.get_keyed_decodable::(HEADER_TIP_KEY)?.map(|(_, h)| h.clone())) - } + /// Find header id with most work. + fn fetch_header_tip(&self) -> Result, Error>; - /// Read header from the DB - pub fn fetch_header(&self, id: &sha256d::Hash) -> Result, Error> { - Ok(self.db.get_hash_keyed::(id)?.map(|(_, header)| header)) - } + /// Read header from the DB. + fn fetch_header(&self, id: BlockHash) -> Result, Error>; - /// 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 @@ -197,52 +92,28 @@ pub struct StoredHeader { pub log2work: f64, } -// need to implement if put_hash_keyed and get_hash_keyed should be used -impl BitcoinHash for StoredHeader { - fn bitcoin_hash(&self) -> sha256d::Hash { - self.header.bitcoin_hash() +impl StoredHeader { + pub fn block_hash(&self) -> BlockHash { + self.header.block_hash() } } -const HEADER_TIP_KEY: &[u8] = &[0u8; 1]; - -#[cfg(test)] -mod test { - use bitcoin::{Network, BitcoinHash}; - use bitcoin_hashes::sha256d::Hash; - 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.bitcoin_hash().eq(&genesis_header.bitcoin_hash())) +impl BitcoinObject for StoredHeader { + fn encode(&self, mut w: W) -> Result { + Ok(self.header.consensus_encode(&mut w)? + + self.height.consensus_encode(&mut w)? + + self.log2work.to_bits().consensus_encode(&mut w)?) } - #[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: Hash = "6cfb35868c4465b7c289d7d5641563aa973db6a929655282a7bf95c8257f53ef".parse().unwrap(); - chaindb.store_header_tip(&missing_tip_header_hash).unwrap(); - - chaindb.init().unwrap(); + fn decode(mut d: D) -> Result { + Ok(StoredHeader { + header: Decodable::consensus_decode(&mut d)?, + height: Decodable::consensus_decode(&mut d)?, + log2work: f64::from_bits(Decodable::consensus_decode(&mut d)?), + }) + } - let header_tip = chaindb.header_tip(); - assert!(header_tip.is_some(), "failed to get header for tip"); - assert!(header_tip.unwrap().stored.bitcoin_hash().eq(&genesis_header.bitcoin_hash())) + fn hash(&self) -> BlockHash { + self.block_hash() } } - - diff --git a/src/constructor.rs b/src/constructor.rs index 748240f..7f20035 100644 --- a/src/constructor.rs +++ b/src/constructor.rs @@ -21,10 +21,10 @@ use bitcoin::{ network::{ - constants::Network + 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; @@ -39,7 +39,6 @@ use futures::{ use std::pin::Pin; use futures_timer::Interval; use crate::headerdownload::HeaderDownload; -#[cfg(feature = "lightning")] use crate::lightning::LightningConnector; use crate::p2p::{P2P, P2PControl, PeerMessageSender, PeerSource}; use crate::ping::Ping; use rand::{RngCore, thread_rng}; @@ -55,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"), '/'); @@ -71,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))) @@ -98,22 +100,20 @@ impl Constructor { let (p2p, p2p_control) = P2P::new(p2pconfig, PeerMessageSender::new(to_dispatcher), BACK_PRESSURE); - #[cfg(feature = "lightning")] let lightning = Arc::new(Mutex::new(LightningConnector::new(network, p2p_control.clone()))); - #[cfg(not(feature = "lightning"))] let lightning = Arc::new(Mutex::new(DownStreamDummy {})); - + let downstream = Arc::new(Mutex::new(DownStreamDummy {})); let timeout = Arc::new(Mutex::new(Timeout::new(p2p_control.clone()))); let mut dispatcher = Dispatcher::new(from_p2p); - dispatcher.add_listener(HeaderDownload::new(chaindb.clone(), p2p_control.clone(), timeout.clone(), lightning.clone())); + dispatcher.add_listener(HeaderDownload::new(chaindb.clone(), p2p_control.clone(), timeout.clone(), downstream.clone())); dispatcher.add_listener(Ping::new(p2p_control.clone(), timeout.clone())); for addr in &listen { p2p_control.send(P2PControl::Bind(addr.clone())); } - Ok(Constructor { p2p, downstream: lightning }) + Ok(Constructor { p2p, downstream }) } /// Run the stack. This should be called AFTER registering listener of the ChainWatchInterface, @@ -141,7 +141,7 @@ impl Constructor { let p2p = self.p2p.clone(); let mut cex = executor.clone(); executor.run(future::poll_fn(move |_| { - let needed_services = 0; + let needed_services = ServiceFlags::NONE; p2p.poll_events("bitcoin", needed_services, &mut cex); Async::Ready(()) })); 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/headercache.rs b/src/headercache.rs index f154157..51ee21d 100644 --- a/src/headercache.rs +++ b/src/headercache.rs @@ -18,15 +18,13 @@ //! use bitcoin::{ - BitcoinHash, blockdata::block::BlockHeader, network::constants::Network, util::{ uint::Uint256, - }, + }, BlockHash, }; -use bitcoin_hashes::sha256d::Hash as Sha256dHash; -use bitcoin_hashes::Hash; +use bitcoin::hashes::Hash; use crate::chaindb::StoredHeader; use crate::error::Error; use log::trace; @@ -37,11 +35,11 @@ use std::{ #[derive(Clone)] pub struct CachedHeader { pub stored : StoredHeader, - id: Sha256dHash + id: BlockHash } impl CachedHeader { - pub fn new (id: &Sha256dHash, header: StoredHeader) -> CachedHeader { + pub fn new (id: &BlockHash, header: StoredHeader) -> CachedHeader { CachedHeader{ stored: header, id: id.clone() } } @@ -78,7 +76,7 @@ impl CachedHeader { if target != required_target { return Err(Error::SpvBadTarget); } - let data: [u8; 32] = self.bitcoin_hash().into_inner(); + let data: [u8; 32] = self.block_hash().into_inner(); let mut ret = [0u64; 4]; LittleEndian::read_u64_into(&data, &mut ret); let hash = &Uint256(ret); @@ -97,8 +95,8 @@ impl CachedHeader { } } -impl BitcoinHash for CachedHeader { - fn bitcoin_hash(&self) -> Sha256dHash { +impl CachedHeader { + pub fn block_hash(&self) -> BlockHash { self.id } } @@ -107,9 +105,9 @@ pub struct HeaderCache { // network network: Network, // all known headers - headers: HashMap, + headers: HashMap, // header chain with most work - trunk: Vec, + trunk: Vec, } const EXPECTED_CHAIN_LENGTH: usize = 600000; @@ -119,7 +117,7 @@ impl HeaderCache { HeaderCache { network, headers: HashMap::with_capacity(EXPECTED_CHAIN_LENGTH), trunk: Vec::with_capacity(EXPECTED_CHAIN_LENGTH) } } - pub fn add_header_unchecked(&mut self, id: &Sha256dHash, stored: &StoredHeader) { + pub fn add_header_unchecked(&mut self, id: &BlockHash, stored: &StoredHeader) { let cached = CachedHeader::new(id, stored.clone()); self.headers.insert(id.clone(), cached); self.trunk.push(id.clone()); @@ -134,12 +132,12 @@ impl HeaderCache { } /// add a Bitcoin header - pub fn add_header(&mut self, header: &BlockHeader) -> Result>, Option>)>, Error> { - if self.headers.get(&header.bitcoin_hash()).is_some() { + pub fn add_header(&mut self, header: &BlockHeader) -> Result>, Option>)>, Error> { + if self.headers.get(&header.block_hash()).is_some() { // ignore already known header return Ok(None); } - if header.prev_blockhash != Sha256dHash::default() { + if header.prev_blockhash != BlockHash::default() { // regular update let previous; if let Some(prev) = self.headers.get(&header.prev_blockhash) { @@ -153,7 +151,7 @@ impl HeaderCache { return Ok(Some(self.add_header_to_tree(&previous, header)?)); } else { // insert genesis - let new_tip = header.bitcoin_hash(); + let new_tip = header.block_hash(); let stored = CachedHeader::new(&new_tip, StoredHeader { header: header.clone(), height: 0, @@ -186,7 +184,7 @@ impl HeaderCache { } // add header to tree, return stored, optional list of unwinds, optional list of extensions - fn add_header_to_tree(&mut self, prev: &CachedHeader, next: &BlockHeader) -> Result<(CachedHeader, Option>, Option>), Error> { + fn add_header_to_tree(&mut self, prev: &CachedHeader, next: &BlockHeader) -> Result<(CachedHeader, Option>, Option>), Error> { const DIFFCHANGE_INTERVAL: u32 = 2016; const DIFFCHANGE_TIMESPAN: u32 = 14 * 24 * 3600; const TARGET_BLOCK_SPACING: u32 = 600; @@ -238,7 +236,7 @@ impl HeaderCache { let mut scan = prev.clone(); let mut height = prev.stored.height; let max_target = Self::max_target(); - while height % DIFFCHANGE_INTERVAL != 0 && scan.stored.header.prev_blockhash != Sha256dHash::default() && scan.stored.header.target() == max_target { + while height % DIFFCHANGE_INTERVAL != 0 && scan.stored.header.prev_blockhash != BlockHash::default() && scan.stored.header.target() == max_target { if let Some(header) = self.headers.get(&scan.stored.header.prev_blockhash) { scan = header.clone(); height = header.stored.height; @@ -253,7 +251,7 @@ impl HeaderCache { prev.stored.header.target() }; - let cached = CachedHeader::new(&next.bitcoin_hash(), StoredHeader { + let cached = CachedHeader::new(&next.block_hash(), StoredHeader { header: next.clone(), height: prev.stored.height + 1, log2work: Self::log2(next.work() + Self::exp2(prev.stored.log2work)) @@ -264,7 +262,7 @@ impl HeaderCache { return Err(Error::SpvBadProofOfWork); } - let next_hash = cached.bitcoin_hash(); + let next_hash = cached.block_hash(); // store header in cache self.headers.insert(next_hash.clone(), cached.clone()); @@ -317,7 +315,7 @@ impl HeaderCache { } /// position on trunk (chain with most work from genesis to tip) - pub fn pos_on_trunk(&self, hash: &Sha256dHash) -> Option { + pub fn pos_on_trunk(&self, hash: &BlockHash) -> Option { self.trunk.iter().rev().position(|e| { *e == *hash }).map(|p| (self.trunk.len() - p - 1) as u32) } @@ -329,7 +327,7 @@ impl HeaderCache { None } - pub fn tip_hash(&self) -> Option { + pub fn tip_hash(&self) -> Option { if let Some(tip) = self.trunk.last() { return Some(*tip); } @@ -355,7 +353,7 @@ impl HeaderCache { } /// Fetch a header by its id from cache - pub fn get_header(&self, id: &Sha256dHash) -> Option { + pub fn get_header(&self, id: &BlockHash) -> Option { if let Some(header) = self.headers.get(id) { return Some(header.clone()); } @@ -386,7 +384,7 @@ impl HeaderCache { } // locator for getheaders message - pub fn locator_hashes(&self) -> Vec { + pub fn locator_hashes(&self) -> Vec { let mut locator = vec!(); let mut skip = 1; let mut count = 0; diff --git a/src/headerdownload.rs b/src/headerdownload.rs index ac04801..83dae63 100644 --- a/src/headerdownload.rs +++ b/src/headerdownload.rs @@ -16,14 +16,13 @@ //! //! # Download headers //! -use bitcoin::{BitcoinHash, network::{ +use bitcoin::{network::{ message::NetworkMessage, - message_blockdata::{GetHeadersMessage, Inventory, InvType}, -}, BlockHeader}; -use bitcoin_hashes::sha256d::Hash as Sha256dHash; + message_blockdata::{GetHeadersMessage, Inventory}, constants::ServiceFlags, +}, BlockHeader, BlockHash}; use crate::chaindb::SharedChainDB; use crate::error::Error; -use crate::p2p::{P2PControl, P2PControlSender, PeerId, PeerMessage, PeerMessageReceiver, PeerMessageSender, SERVICE_BLOCKS}; +use crate::p2p::{P2PControl, P2PControlSender, PeerId, PeerMessage, PeerMessageReceiver, PeerMessageSender}; use log::{info, trace, debug, error}; use std::{ collections::VecDeque, @@ -86,7 +85,7 @@ impl HeaderDownload { fn is_serving_blocks(&self, peer: PeerId) -> bool { if let Some(peer_version) = self.p2p.peer_version(peer) { - return peer_version.services & SERVICE_BLOCKS != 0; + return peer_version.services.has(ServiceFlags::NETWORK); } false } @@ -96,10 +95,10 @@ impl HeaderDownload { let mut ask_for_headers = false; for inventory in v { // only care for blocks - if inventory.inv_type == InvType::Block { + if let Inventory::Block(hash) = inventory { let chaindb = self.chaindb.read().unwrap(); - if chaindb.get_header(&inventory.hash).is_none() { - debug!("received inv for new block {} peer={}", inventory.hash, peer); + if chaindb.get_header(&hash).is_none() { + debug!("received inv for new block {} peer={}", hash, peer); // ask for header(s) if observing a new block ask_for_headers = true; } @@ -122,7 +121,7 @@ impl HeaderDownload { let first = if locator.len() > 0 { *locator.first().unwrap() } else { - Sha256dHash::default() + BlockHash::default() }; self.timeout.lock().unwrap().expect(peer, 1, ExpectedReply::Headers); self.p2p.send_network(peer, NetworkMessage::GetHeaders(GetHeadersMessage::new(locator, first))); @@ -182,7 +181,7 @@ impl HeaderDownload { return Ok(()); } Err(e) => { - debug!("error {} processing header {} ", e, header.bitcoin_hash()); + debug!("error {} processing header {} ", e, header.block_hash()); return Ok(()); } } @@ -208,7 +207,7 @@ impl HeaderDownload { info!("received {} headers new tip={} from peer={}", headers.len(), new_tip, peer); self.p2p.send(P2PControl::Height(height)); } else { - debug!("received {} known or orphan headers [{} .. {}] from peer={}", headers.len(), headers[0].bitcoin_hash(), headers[headers.len()-1].bitcoin_hash(), peer); + debug!("received {} known or orphan headers [{} .. {}] from peer={}", headers.len(), headers[0].block_hash(), headers[headers.len()-1].block_hash(), peer); } } Ok(()) diff --git a/src/lib.rs b/src/lib.rs index c5da572..d393a3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,6 @@ #![deny(unused_must_use)] #![forbid(unsafe_code)] -#[cfg(feature="lightning")] mod lightning; mod headercache; pub mod ping; @@ -38,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/lightning.rs b/src/lightning.rs deleted file mode 100644 index 684ebe7..0000000 --- a/src/lightning.rs +++ /dev/null @@ -1,122 +0,0 @@ -// -// 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. -// -//! -//! # Connector to serve a lightning network implementation -//! -//! This implements an interface to higher level applications -//! - -use bitcoin::{ - blockdata::{ - block::{Block, BlockHeader}, - transaction::Transaction, - script::Script, - }, - network::{ - message::NetworkMessage, - constants::Network - } -}; - -use bitcoin_hashes::sha256d::Hash as Sha256dHash; - -use lightning::{ - chain::chaininterface::{ChainListener, ChainWatchInterface, ChainWatchInterfaceUtil,ChainError}, - util::logger::{Level, Logger, Record} -}; - -use downstream::Downstream; - -use p2p::P2PControlSender; - -use std::sync::{Arc, Weak, Mutex}; - -struct LightningLogger{ - level: Level -} - -impl Logger for LightningLogger { - fn log(&self, record: &Record) { - if self.level >= record.level { - debug!("{:<5} [{} : {}, {}] {}", record.level.to_string(), record.module_path, record.file, record.line, record.args); - } - } -} - -pub type SharedLightningConnector = Arc>; - -/// connector to lightning network -pub struct LightningConnector { - util: ChainWatchInterfaceUtil, - p2p: P2PControlSender -} - -impl Downstream for LightningConnector { - /// called by the node if new block added to trunk (longest chain) - /// this will notify listeners on lightning side - fn block_connected(&mut self, block: &Block, height: u32) { - self.util.block_connected_with_filtering(block, height) - } - - fn header_connected(&mut self, block: &BlockHeader, height: u32) {} - - /// called by the node if a block is removed from trunk (orphaned from longest chain) - /// this will notify listeners on lightning side - fn block_disconnected(&mut self, header: &BlockHeader) { - self.util.block_disconnected(header) - } -} - -impl LightningConnector { - /// create a connector - pub fn new (network: Network, p2p: P2PControlSender) -> LightningConnector { - LightningConnector { - util: ChainWatchInterfaceUtil::new(network, Arc::new(LightningLogger{level: Level::Info})), - p2p - } - } - - /// broadcast transaction to all connected peers - pub fn broadcast (&self, tx: Transaction) { - self.p2p.broadcast(NetworkMessage::Tx(tx)) - } -} - -impl ChainWatchInterface for LightningConnector { - - fn install_watch_tx(&self, _txid: &Sha256dHash, _script_pub_key: &Script) { - unimplemented!() - } - - /// install a listener to be called with transactions that spend the outpoint - fn install_watch_outpoint(&self, outpoint: (Sha256dHash, u32), out_script: &Script) { - self.util.install_watch_outpoint(outpoint, out_script) - } - - /// install a listener to be called for every transaction - fn watch_all_txn(&self) { - self.util.watch_all_txn() - } - - /// install a listener for blocks added to or removed from trunk - fn register_listener(&self, listener: Weak) { - self.util.register_listener(listener) - } - - fn get_chain_utxo(&self, _genesis_hash: Sha256dHash, _unspent_tx_output_identifier: u64) -> Result<(Script, u64), ChainError> { - Err(ChainError::NotSupported) - } -} \ No newline at end of file diff --git a/src/p2p.rs b/src/p2p.rs index 1cf04e6..c2bfd4e 100644 --- a/src/p2p.rs +++ b/src/p2p.rs @@ -24,7 +24,7 @@ use bitcoin::{ }; use bitcoin::network::{ address::Address, - constants::Network, + constants::{ServiceFlags, Network}, message::{NetworkMessage, RawNetworkMessage}, message_network::VersionMessage }; @@ -191,7 +191,7 @@ pub trait Command { impl Command for RawNetworkMessage { fn command(&self) -> String { - self.command() + self.command().to_string() } } @@ -205,7 +205,7 @@ pub struct VersionCarrier { /// The P2P network protocol version pub version: u32, /// A bitmask describing the services supported by this node - pub services: u64, + pub services: ServiceFlags, /// The time at which the `version` message was sent pub timestamp: u64, /// The network address of the peer receiving the message @@ -300,11 +300,11 @@ impl P2PConfig for BitcoinP2PConfig { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64; let services = if !self.server { - 0 + ServiceFlags::NONE } else { - SERVICE_BLOCKS + SERVICE_WITNESS + + ServiceFlags::NETWORK | ServiceFlags::WITNESS | // announce that this node is capable of serving BIP157 messages - SERVICE_FILTERS + ServiceFlags::COMPACT_FILTERS }; // build message @@ -312,9 +312,9 @@ impl P2PConfig for BitcoinP2PConfig { version: min(max_protocol_version, self.max_protocol_version), services, timestamp, - receiver: Address::new(remote, 1), + receiver: Address::new(remote, ServiceFlags::NETWORK), // sender is only dummy - sender: Address::new(remote, 1), + sender: Address::new(remote, ServiceFlags::NETWORK), nonce: self.nonce, user_agent: self.user_agent.clone(), start_height: self.height.load(Ordering::Relaxed) as i32, @@ -675,7 +675,7 @@ impl Result<(), Error> { + fn event_processor (&self, event: Event, pid: PeerId, needed_services: ServiceFlags, iobuf: &mut [u8]) -> Result<(), Error> { let readiness = UnixReady::from(event.readiness()); // check for error first if readiness.is_hup() || readiness.is_error() { @@ -792,8 +792,8 @@ impl { + self.p2p.send_network(pid, NetworkMessage::Pong(nonce)); + } _ => { } } }