Skip to content

Commit f8902dd

Browse files
committed
Convert ChainDB to a trait implemented by Hammersbald struct. Make hammersbald default optional dependency.
1 parent fc6205e commit f8902dd

File tree

6 files changed

+279
-186
lines changed

6 files changed

+279
-186
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
target
33
*.iml
44
Cargo.lock
5+
client.db.*

Cargo.toml

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ keywords = [ "bitcoin" ]
1111
readme = "README.md"
1212
edition = "2018"
1313

14+
[features]
15+
default = ["hammersbald"]
16+
1417
[lib]
1518
name = "murmel"
1619
path = "src/lib.rs"
@@ -19,7 +22,6 @@ path = "src/lib.rs"
1922
lightning = { version ="0.0.9", optional=true }
2023
bitcoin = { version= "0.21", features=["use-serde"]}
2124
bitcoin_hashes = "0.7"
22-
hammersbald = { version= "2.4", features=["bitcoin_support"]}
2325
mio = "0.6"
2426
rand = "0.7"
2527
log = "0.4"
@@ -31,6 +33,9 @@ futures-timer = "0.3"
3133
serde="1"
3234
serde_derive="1"
3335

36+
## optional
37+
hammersbald = { version= "2.4", features=["bitcoin_support"], optional=true }
38+
3439
[dev-dependencies]
3540
rustc-serialize = "0.3"
3641
hex = "0.3"

src/chaindb.rs

+34-182
Original file line numberDiff line numberDiff line change
@@ -14,176 +14,69 @@
1414
// limitations under the License.
1515
//
1616
//!
17-
//! # Blockchain DB for a node
17+
//! # Blockchain DB API for a node
1818
//!
1919
2020
use std::sync::{Arc, RwLock};
21-
use std::path::Path;
2221

23-
use bitcoin::{BitcoinHash, Network};
22+
use bitcoin::BitcoinHash;
2423
use bitcoin::blockdata::block::BlockHeader;
25-
use bitcoin::blockdata::constants::genesis_block;
2624

2725
use bitcoin_hashes::sha256d;
28-
use hammersbald::{BitcoinAdaptor, HammersbaldAPI, persistent, transient};
2926

3027
use crate::error::Error;
31-
use crate::headercache::{CachedHeader, HeaderCache};
32-
use log::{debug, info, warn, error};
28+
use crate::headercache::CachedHeader;
29+
3330
use serde_derive::{Serialize, Deserialize};
3431

3532
/// Shared handle to a database storing the block chain
3633
/// protected by an RwLock
37-
pub type SharedChainDB = Arc<RwLock<ChainDB>>;
38-
39-
/// Database storing the block chain
40-
pub struct ChainDB {
41-
db: BitcoinAdaptor,
42-
headercache: HeaderCache,
43-
network: Network,
44-
}
45-
46-
impl ChainDB {
47-
/// Create an in-memory database instance
48-
pub fn mem(network: Network) -> Result<ChainDB, Error> {
49-
info!("working with in memory chain db");
50-
let db = BitcoinAdaptor::new(transient(2)?);
51-
let headercache = HeaderCache::new(network);
52-
Ok(ChainDB { db, network, headercache })
53-
}
34+
pub type SharedChainDB = Arc<RwLock<Box<dyn ChainDB>>>;
5435

55-
/// Create or open a persistent database instance identified by the path
56-
pub fn new(path: &Path, network: Network) -> Result<ChainDB, Error> {
57-
let basename = path.to_str().unwrap().to_string();
58-
let db = BitcoinAdaptor::new(persistent((basename.clone()).as_str(), 100, 2)?);
59-
let headercache = HeaderCache::new(network);
60-
Ok(ChainDB { db, network, headercache })
61-
}
36+
/// Blockchain DB API for a client node.
37+
pub trait ChainDB: Send + Sync {
6238

63-
/// Initialize caches
64-
pub fn init(&mut self) -> Result<(), Error> {
65-
self.init_headers()?;
66-
Ok(())
67-
}
39+
/// Initialize caches.
40+
fn init(&mut self) -> Result<(), Error>;
6841

6942
/// Batch updates. Updates are permanent after finishing a batch.
70-
pub fn batch(&mut self) -> Result<(), Error> {
71-
self.db.batch()?;
72-
Ok(())
73-
}
43+
fn batch(&mut self) -> Result<(), Error>;
7444

75-
fn init_headers(&mut self) -> Result<(), Error> {
76-
if let Some(tip) = self.fetch_header_tip()? {
77-
info!("reading stored header chain from tip {}", tip);
78-
if self.fetch_header(&tip)?.is_some() {
79-
let mut h = tip;
80-
while let Some(stored) = self.fetch_header(&h)? {
81-
debug!("read stored header {}", &stored.bitcoin_hash());
82-
self.headercache.add_header_unchecked(&h, &stored);
83-
if stored.header.prev_blockhash != sha256d::Hash::default() {
84-
h = stored.header.prev_blockhash;
85-
} else {
86-
break;
87-
}
88-
}
89-
self.headercache.reverse_trunk();
90-
info!("read {} headers", self.headercache.len());
91-
} else {
92-
warn!("unable to read header for tip {}", tip);
93-
self.init_to_genesis()?;
94-
}
95-
} else {
96-
info!("no header tip found");
97-
self.init_to_genesis()?;
98-
}
99-
Ok(())
100-
}
45+
/// Store a header.
46+
fn add_header(&mut self, header: &BlockHeader) -> Result<Option<(StoredHeader, Option<Vec<sha256d::Hash>>, Option<Vec<sha256d::Hash>>)>, Error>;
10147

102-
fn init_to_genesis(&mut self) -> Result<(), Error> {
103-
let genesis = genesis_block(self.network).header;
104-
if let Some((cached, _, _)) = self.headercache.add_header(&genesis)? {
105-
info!("initialized with genesis header {}", genesis.bitcoin_hash());
106-
self.db.put_hash_keyed(&cached.stored)?;
107-
self.db.batch()?;
108-
self.store_header_tip(&cached.bitcoin_hash())?;
109-
self.db.batch()?;
110-
} else {
111-
error!("failed to initialize with genesis header");
112-
return Err(Error::NoTip);
113-
}
114-
Ok(())
115-
}
48+
/// Return position of hash on trunk if hash is on trunk.
49+
fn pos_on_trunk(&self, hash: &sha256d::Hash) -> Option<u32>;
11650

117-
/// Store a header
118-
pub fn add_header(&mut self, header: &BlockHeader) -> Result<Option<(StoredHeader, Option<Vec<sha256d::Hash>>, Option<Vec<sha256d::Hash>>)>, Error> {
119-
if let Some((cached, unwinds, forward)) = self.headercache.add_header(header)? {
120-
self.db.put_hash_keyed(&cached.stored)?;
121-
if let Some(forward) = forward.clone() {
122-
if forward.len() > 0 {
123-
self.store_header_tip(forward.last().unwrap())?;
124-
}
125-
}
126-
return Ok(Some((cached.stored, unwinds, forward)));
127-
}
128-
Ok(None)
129-
}
51+
/// Iterate trunk [from .. tip].
52+
fn iter_trunk<'a>(&'a self, from: u32) -> Box<dyn Iterator<Item=&'a CachedHeader> + 'a>;
13053

131-
/// return position of hash on trunk if hash is on trunk
132-
pub fn pos_on_trunk(&self, hash: &sha256d::Hash) -> Option<u32> {
133-
self.headercache.pos_on_trunk(hash)
134-
}
54+
/// Iterate trunk [genesis .. from] in reverse order from is the tip if not specified.
55+
fn iter_trunk_rev<'a>(&'a self, from: Option<u32>) -> Box<dyn Iterator<Item=&'a CachedHeader> + 'a>;
13556

136-
/// iterate trunk [from .. tip]
137-
pub fn iter_trunk<'a>(&'a self, from: u32) -> impl Iterator<Item=&'a CachedHeader> + 'a {
138-
self.headercache.iter_trunk(from)
139-
}
57+
/// Retrieve the id of the block/header with most work.
58+
fn header_tip(&self) -> Option<CachedHeader>;
14059

141-
/// iterate trunk [genesis .. from] in reverse order from is the tip if not specified
142-
pub fn iter_trunk_rev<'a>(&'a self, from: Option<u32>) -> impl Iterator<Item=&'a CachedHeader> + 'a {
143-
self.headercache.iter_trunk_rev(from)
144-
}
60+
/// Fetch a header by its id from cache.
61+
fn get_header(&self, id: &sha256d::Hash) -> Option<CachedHeader>;
14562

146-
/// retrieve the id of the block/header with most work
147-
pub fn header_tip(&self) -> Option<CachedHeader> {
148-
self.headercache.tip()
149-
}
63+
/// Fetch a header by its id from cache.
64+
fn get_header_for_height(&self, height: u32) -> Option<CachedHeader>;
15065

151-
/// Fetch a header by its id from cache
152-
pub fn get_header(&self, id: &sha256d::Hash) -> Option<CachedHeader> {
153-
self.headercache.get_header(id)
154-
}
66+
/// Locator for getheaders message.
67+
fn header_locators(&self) -> Vec<sha256d::Hash>;
15568

156-
/// Fetch a header by its id from cache
157-
pub fn get_header_for_height(&self, height: u32) -> Option<CachedHeader> {
158-
self.headercache.get_header_for_height(height)
159-
}
69+
/// Store the header id with most work.
70+
fn store_header_tip(&mut self, tip: &sha256d::Hash) -> Result<(), Error>;
16071

161-
/// locator for getheaders message
162-
pub fn header_locators(&self) -> Vec<sha256d::Hash> {
163-
self.headercache.locator_hashes()
164-
}
72+
/// Find header id with most work.
73+
fn fetch_header_tip(&self) -> Result<Option<sha256d::Hash>, Error>;
16574

166-
/// Store the header id with most work
167-
pub fn store_header_tip(&mut self, tip: &sha256d::Hash) -> Result<(), Error> {
168-
self.db.put_keyed_encodable(HEADER_TIP_KEY, tip)?;
169-
Ok(())
170-
}
75+
/// Read header from the DB.
76+
fn fetch_header(&self, id: &sha256d::Hash) -> Result<Option<StoredHeader>, Error>;
17177

172-
/// Find header id with most work
173-
pub fn fetch_header_tip(&self) -> Result<Option<sha256d::Hash>, Error> {
174-
Ok(self.db.get_keyed_decodable::<sha256d::Hash>(HEADER_TIP_KEY)?.map(|(_, h)| h.clone()))
175-
}
176-
177-
/// Read header from the DB
178-
pub fn fetch_header(&self, id: &sha256d::Hash) -> Result<Option<StoredHeader>, Error> {
179-
Ok(self.db.get_hash_keyed::<StoredHeader>(id)?.map(|(_, header)| header))
180-
}
181-
182-
/// Shutdown db
183-
pub fn shutdown(&mut self) {
184-
self.db.shutdown();
185-
debug!("shutdown chain db")
186-
}
78+
/// Shutdown the DB.
79+
fn shutdown(&mut self);
18780
}
18881

18982
/// A header enriched with information about its position on the blockchain
@@ -204,45 +97,4 @@ impl BitcoinHash for StoredHeader {
20497
}
20598
}
20699

207-
const HEADER_TIP_KEY: &[u8] = &[0u8; 1];
208-
209-
#[cfg(test)]
210-
mod test {
211-
use bitcoin::{Network, BitcoinHash};
212-
use bitcoin_hashes::sha256d::Hash;
213-
use bitcoin::blockdata::constants::genesis_block;
214-
215-
use crate::chaindb::ChainDB;
216-
217-
#[test]
218-
fn init_tip_header() {
219-
let network = Network::Testnet;
220-
let genesis_header = genesis_block(network).header;
221-
222-
let mut chaindb = ChainDB::mem(network).unwrap();
223-
chaindb.init().unwrap();
224-
chaindb.init().unwrap();
225-
226-
let header_tip = chaindb.header_tip();
227-
assert!(header_tip.is_some(), "failed to get header for tip");
228-
assert!(header_tip.unwrap().stored.bitcoin_hash().eq(&genesis_header.bitcoin_hash()))
229-
}
230-
231-
#[test]
232-
fn init_recover_if_missing_tip_header() {
233-
let network = Network::Testnet;
234-
let genesis_header = genesis_block(network).header;
235-
236-
let mut chaindb = ChainDB::mem(network).unwrap();
237-
let missing_tip_header_hash: Hash = "6cfb35868c4465b7c289d7d5641563aa973db6a929655282a7bf95c8257f53ef".parse().unwrap();
238-
chaindb.store_header_tip(&missing_tip_header_hash).unwrap();
239-
240-
chaindb.init().unwrap();
241-
242-
let header_tip = chaindb.header_tip();
243-
assert!(header_tip.is_some(), "failed to get header for tip");
244-
assert!(header_tip.unwrap().stored.bitcoin_hash().eq(&genesis_header.bitcoin_hash()))
245-
}
246-
}
247-
248100

src/constructor.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use bitcoin::{
2424
constants::Network
2525
}
2626
};
27-
use crate::chaindb::{ChainDB, SharedChainDB};
27+
use crate::hammersbald::Hammersbald;
2828
use crate::dispatcher::Dispatcher;
2929
use crate::dns::dns_seed;
3030
use crate::error::Error;
@@ -55,6 +55,7 @@ use bitcoin::network::message::NetworkMessage;
5555
use bitcoin::network::message::RawNetworkMessage;
5656
use crate::p2p::BitcoinP2PConfig;
5757
use std::time::Duration;
58+
use crate::chaindb::SharedChainDB;
5859

5960
const MAX_PROTOCOL_VERSION: u32 = 70001;
6061
const USER_AGENT: &'static str = concat!("/Murmel:", env!("CARGO_PKG_VERSION"), '/');
@@ -71,9 +72,11 @@ impl Constructor {
7172
pub fn open_db(path: Option<&Path>, network: Network, _birth: u64) -> Result<SharedChainDB, Error> {
7273
let mut chaindb =
7374
if let Some(path) = path {
74-
ChainDB::new(path, network)?
75+
#[cfg(feature = "default")]
76+
Hammersbald::new(path, network)?
7577
} else {
76-
ChainDB::mem(network)?
78+
#[cfg(feature = "default")]
79+
Hammersbald::mem(network)?
7780
};
7881
chaindb.init()?;
7982
Ok(Arc::new(RwLock::new(chaindb)))

0 commit comments

Comments
 (0)