用Rust实现区块链 - 3 持久化

前两部分我们都是将区块链存储在内存中。从这一部分开始,我们将区块链持久化到硬盘中,默认使用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到灯火阑珊

猜你喜欢

转载自blog.csdn.net/lonewolf79218/article/details/124489729
今日推荐