前两部分我们都是将区块链存储在内存中。从这一部分开始,我们将区块链持久化到硬盘中,默认使用KV数据库sled。
数据库结构
KEY | VALUE | tip_hash | 区块链中最后加入块的hash值 | height | 区块链中的高度 | blocks:{hash} | 区块的hash值,blocks为前缀。 |
pub const TIP_KEY: &str = "tip_hash";
pub const HEIGHT: &str = "height";
pub const TABLE_OF_BLOCK: &str = "blocks";
Storage Trait
考虑到存储的可扩展性,将来使用其他KV数据库,如:RocksDB。我们定义了storage trait。
pub trait Storage: Send + Sync + 'static {
// 获取最后一个块的hash值
fn get_tip(&self) -> Result<Option<String>, BlockchainError>;
// 获取一个区块
fn get_block(&self, key: &str) -> Result<Option<Block>, BlockchainError>;
// 获取区块链的高度
fn get_height(&self) -> Result<Option<usize>, BlockchainError>;
// 以事务的方式更新区块链
fn update_blocks(&self, key: &str, block: &Block, height: usize);
// 获取区块的迭代器
fn get_block_iter(&self) -> Result<Box<dyn Iterator<Item = Block>>, BlockchainError>;
}
// 定义区块的迭代器
pub struct StorageIterator<T> {
data: T
}
impl<T> StorageIterator<T> {
pub fn new(data: T) -> Self {
Self { data }
}
}
// T泛型需要满足Iterator约束
// T的item类型需要满足能转换成Block
impl<T> Iterator for StorageIterator<T>
where
T: Iterator,
T::Item: Into<Block>
{
type Item = Block;
fn next(&mut self) -> Option<Self::Item> {
self.data.next().map(|v| v.into())
}
}
Sled的实现
pub struct SledDb {
// seld::Db
db: Db
}
impl SledDb {
pub fn new(path: impl AsRef<Path>) -> Self {
Self {
db: sled::open(path).unwrap()
}
}
fn get_full_key(table: &str, key: &str) -> String {
format!("{}:{}", table, key)
}
}
impl Storage for SledDb {
......
fn update_blocks(&self, key: &str, block: &Block, height: usize) {
// 使用事务
let _: TransactionResult<(), ()> = self.db.transaction(|db| {
let name = Self::get_full_key(TABLE_OF_BLOCK, key);
db.insert(name.as_str(), serialize(block).unwrap())?;
db.insert(TIP_KEY, serialize(key).unwrap())?;
db.insert(HEIGHT, serialize(&height).unwrap())?;
db.flush();
Ok(())
});
}
fn get_block_iter(&self) -> Result<Box<dyn Iterator<Item = Block>>, BlockchainError> {
let prefix = format!("{}:", TABLE_OF_BLOCK);
let iter = StorageIterator::new(self.db.scan_prefix(prefix));
Ok(Box::new(iter))
}
}
修改区块链
// 默认使用sled数据库
pub struct Blockchain<T = SledDb> {
storage: T,
tip: Arc<RwLock<String>>,
height: AtomicUsize,
}
impl<T: Storage> Blockchain<T> {
pub fn new(storage: T) -> Self {
// 如果数据库中有tip值,则加载到内存。
// 否则创建一个创世块,并更新到数据库中。
if let Ok(Some(tip)) = storage.get_tip() {
let height = storage.get_height().unwrap();
Self {
storage,
tip: Arc::new(RwLock::new(tip)),
height: AtomicUsize::new(height.unwrap()),
}
}else {
let genesis_block = Block::create_genesis_block(CURR_BITS);
let hash = genesis_block.get_hash();
storage.update_blocks(&hash, &genesis_block, 0 as usize);
Self {
storage,
tip: Arc::new(RwLock::new(hash)),
height: AtomicUsize::new(0),
}
}
}
pub fn mine_block(&mut self, data: &str) {
let block = Block::new(data, &self.tip.read().unwrap(), CURR_BITS);
let hash = block.get_hash();
self.height.fetch_add(1, Ordering::Relaxed);
self.storage.update_blocks(&hash, &block, self.height.load(Ordering::Relaxed));
let mut tip = self.tip.write().unwrap();
*tip = hash;
}
pub fn blocks_info(&self) {
let blocks = self.storage.get_block_iter().unwrap();
for block in blocks {
info!("{:#?}", block);
}
}
}
验证
RUST_LOG=info cargo run --example gen_bc --quiet
INFO blockchain_rust_part_3::blocks::blockchain: Block {
header: BlockHeader {
timestamp: 1650259594,
prev_hash: "",
bits: 8,
nonce: 233,
},
data: "创世区块",
hash: "00d76473e80522e336a1078227d10d599190d8ef6877fa1d6fa980d692ef3c18",
}
第一次执行,创建了创世块,且存入到了数据库中。再次执行,取出的区块信息应该与第一次执行时创建的区块一样。大家可以自己验证。
工程结构
完整代码:
https://github.com/Justin02180218/blockchain_rust
更多文章,请关注公众号:coding到灯火阑珊
|