Skip to content
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
target
*.iml
Cargo.lock
client.db.*
9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
1 change: 1 addition & 0 deletions src/bin/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)));
}
Expand Down
237 changes: 54 additions & 183 deletions src/chaindb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<RwLock<ChainDB>>;
pub type SharedChainDB = Arc<RwLock<Box<dyn ChainDB>>>;

/// 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<ChainDB, Error> {
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<ChainDB, 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(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<(StoredHeader, Option<Vec<BlockHash>>, Option<Vec<BlockHash>>)>, 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<(StoredHeader, Option<Vec<sha256d::Hash>>, Option<Vec<sha256d::Hash>>)>, 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<u32> {
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<u32>;

/// iterate trunk [from .. tip]
pub fn iter_trunk<'a>(&'a self, from: u32) -> impl Iterator<Item=&'a CachedHeader> + 'a {
self.headercache.iter_trunk(from)
}
/// Iterate trunk [from .. tip].
fn iter_trunk<'a>(&'a self, from: u32) -> Box<dyn Iterator<Item=&'a CachedHeader> + '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<u32>) -> impl Iterator<Item=&'a CachedHeader> + '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<u32>) -> Box<dyn Iterator<Item=&'a CachedHeader> + 'a>;

/// retrieve the id of the block/header with most work
pub fn header_tip(&self) -> Option<CachedHeader> {
self.headercache.tip()
}
/// Retrieve the id of the block/header with most work.
fn header_tip(&self) -> Option<CachedHeader>;

/// Fetch a header by its id from cache
pub fn get_header(&self, id: &sha256d::Hash) -> Option<CachedHeader> {
self.headercache.get_header(id)
}
/// Fetch a header by its id from cache.
fn get_header(&self, id: &BlockHash) -> Option<CachedHeader>;

/// Fetch a header by its id from cache
pub fn get_header_for_height(&self, height: u32) -> Option<CachedHeader> {
self.headercache.get_header_for_height(height)
}
/// Fetch a header by its id from cache.
fn get_header_for_height(&self, height: u32) -> Option<CachedHeader>;

/// locator for getheaders message
pub fn header_locators(&self) -> Vec<sha256d::Hash> {
self.headercache.locator_hashes()
}
/// Locator for getheaders message.
fn header_locators(&self) -> Vec<BlockHash>;

/// 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<Option<sha256d::Hash>, Error> {
Ok(self.db.get_keyed_decodable::<sha256d::Hash>(HEADER_TIP_KEY)?.map(|(_, h)| h.clone()))
}
/// Find header id with most work.
fn fetch_header_tip(&self) -> Result<Option<BlockHash>, Error>;

/// Read header from the DB
pub fn fetch_header(&self, id: &sha256d::Hash) -> Result<Option<StoredHeader>, Error> {
Ok(self.db.get_hash_keyed::<StoredHeader>(id)?.map(|(_, header)| header))
}
/// Read header from the DB.
fn fetch_header(&self, id: BlockHash) -> Result<Option<StoredHeader>, 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
Expand All @@ -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<BlockHash> for StoredHeader {
fn encode<W: io::Write>(&self, mut w: W) -> Result<usize, io::Error> {
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<D: io::Read>(mut d: D) -> Result<Self, hammersbald::Error> {
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()
}
}


Loading