hammersbald 3.0.1

Hammersbald - fast persistent store for a blockchain
Documentation
//
// 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.
//
//!
//! # Hammersbald bitcoin support
//!

use std::io;
use std::marker::PhantomData;

use bitcoin_hashes::Hash;
use bitcoin::{Block, BlockHash, BlockHeader, Transaction, Txid, Wtxid};
use bitcoin::consensus::encode::{Decodable, Encodable, serialize, deserialize};

use Error;
use HammersbaldAPI;
use HammersbaldIterator;
use PRef;

/// A trait implemented for Bitcoin object that should be retrievable
/// by a hash identifier.
///
/// Multiple hash identifiers are possible, f.e. a [Transaction] can be stored
/// by either [Txid] or [Wtxid]. Note that a [Transaction] is not automatically
/// stored by both hashes; one must be selected as a type parameter.
pub trait BitcoinObject<Key>: Sized where Key: Hash, <Key as Hash>::Engine: io::Write {
    /// Encode the object into the writer.
    ///
    /// Should not return errors except passthrough errors from the writer.
    fn encode<W: io::Write>(&self, w: W) -> Result<usize, io::Error>;

    /// Decode an object from the reader.
    fn decode<D: io::Read>(d: D) -> Result<Self, Error>;

    /// The key of the item.
    fn hash(&self) -> Key {
        let mut engine = <Key as Hash>::engine();
        self.encode(&mut engine).expect("engines don't error");
        <Key as Hash>::from_engine(engine)
    }

    /// Encode into bytes.
    fn to_bytes(&self) -> Vec<u8> {
        let mut buf = Vec::new();
        self.encode(&mut buf).expect("vecs don't error");
        buf
    }

    /// Convert object from bytes.
    fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
        let mut decoder = io::Cursor::new(bytes);
        let object = Self::decode(&mut decoder)?;

        if decoder.position() as usize != bytes.len() {
            return Err(Error::Corrupted("corrupt data".to_owned()));
        }

        Ok(object)
    }
}

macro_rules! bitcoin_encode {
    () => {
        fn encode<W: io::Write>(&self, w: W) -> Result<usize, io::Error> {
            Ok(bitcoin::consensus::encode::Encodable::consensus_encode(self, w)?)
        }
        fn decode<D: io::Read>(d: D) -> Result<Self, Error> {
            Ok(bitcoin::consensus::encode::Decodable::consensus_decode(d)?)
        }
    };
}

impl BitcoinObject<Txid> for Transaction {
    bitcoin_encode!();
    fn hash(&self) -> Txid {
        self.txid()
    }
}
impl BitcoinObject<Wtxid> for Transaction { bitcoin_encode!(); }

impl BitcoinObject<BlockHash> for BlockHeader { bitcoin_encode!(); }
impl BitcoinObject<BlockHash> for Block {
    bitcoin_encode!();
    fn hash(&self) -> BlockHash {
        BitcoinObject::hash(&self.header)
    }
}

/// Bitcoin adaptor
pub struct BitcoinAdaptor {
    hammersbald: Box<dyn HammersbaldAPI>
}

impl BitcoinAdaptor {
    /// Create a new Adaptor
    pub fn new(hammersbald: Box<dyn HammersbaldAPI>) -> BitcoinAdaptor {
        BitcoinAdaptor { hammersbald }
    }

    /// Store some bitcoin object that has a bitcoin hash
    pub fn put_object_by_hash<H, T>(&mut self, object: &T) -> Result<PRef, Error>
        where H: Hash, <H as Hash>::Engine: io::Write, T: BitcoinObject<H>
    {
        Ok(self.hammersbald.put_keyed(&object.hash()[..], &object.to_bytes()[..])?)
    }

    /// Retrieve a bitcoin object with its hash
    pub fn get_object_by_hash<H, T>(&self, id: H) -> Result<Option<(PRef, T)>, Error>
        where H: Hash, <H as Hash>::Engine: io::Write, T: BitcoinObject<H>
    {
        match self.hammersbald.get_keyed(&id[..])? {
            Some((pref, data)) => Ok(Some((pref, BitcoinObject::from_bytes(&data[..])?))),
            None => Ok(None),
        }
    }

    /// Store some bitcoin object
    pub fn put_object<T>(&mut self, object: &T) -> Result<PRef, Error>
        where T: Encodable
    {
        self.hammersbald.put(&serialize(object))
    }

    /// Retrieve some bitcoin object
    pub fn get_object<T>(&self, pref: PRef) -> Result<(Vec<u8>, T), Error>
        where T: Decodable
    {
        let (key, data) = self.hammersbald.get(pref)?;
        Ok((key, deserialize(&data[..])?))
    }

    /// Store some bitcoin object with arbitary key.
    pub fn put_object_by_key<T>(&mut self, key: &[u8], object: &T) -> Result<PRef, Error>
        where T: Encodable
    {
        Ok(self.hammersbald.put_keyed(key, &serialize(object))?)
    }

    /// Retrieve some bitcoin object with arbitary key
    pub fn get_object_by_key<T>(&self, key: &[u8]) -> Result<Option<(PRef, T)>, Error>
        where T: Decodable
    {
        if let Some((pref, data)) = self.hammersbald.get_keyed(key)? {
            return Ok(Some((pref, deserialize(&data[..])?)));
        }
        Ok(None)
    }

    /// quick check if the db contains a key. This might return false positive.
    pub fn may_have_hash<H: Hash>(&self, key: H) -> Result<bool, Error> {
        Ok(self.hammersbald.may_have_key(&key[..])?)
    }

    /// iterate over all data, useful only if data is homogenous
    pub fn iter_decodable<T> (&self) -> HammersbaldDecodableIterator<T>
        where T: Decodable + ?Sized
    {
        HammersbaldDecodableIterator{
            inner: self.iter(),
            data: PhantomData,
        }
    }
}

/// An iterator over a stream of decodable data.
pub struct HammersbaldDecodableIterator<'a, T> {
    inner: HammersbaldIterator<'a>,
    data: PhantomData<T>
}

impl<'a, T: Decodable> Iterator for HammersbaldDecodableIterator<'a, T> {
    type Item = (PRef, T);

    fn next(&mut self) -> Option<<Self as Iterator>::Item> {
        while let Some((pref, _, data)) = self.inner.next() {
            if let Ok(d) = deserialize(&data[..]) {
                return Some((pref, d));
            }
        }
        None
    }
}

impl HammersbaldAPI for BitcoinAdaptor {
    fn batch(&mut self) -> Result<(), Error> {
        self.hammersbald.batch()
    }

    fn shutdown(&mut self) {
        self.hammersbald.shutdown()
    }

    fn put_keyed(&mut self, key: &[u8], data: &[u8]) -> Result<PRef, Error> {
        self.hammersbald.put_keyed(key, data)
    }

    fn get_keyed(&self, key: &[u8]) -> Result<Option<(PRef, Vec<u8>)>, Error> {
        self.hammersbald.get_keyed(key)
    }

    fn put(&mut self, data: &[u8]) -> Result<PRef, Error> {
        self.hammersbald.put(data)
    }

    fn get(&self, pref: PRef) -> Result<(Vec<u8>, Vec<u8>), Error> {
        self.hammersbald.get(pref)
    }

    fn may_have_key(&self, key: &[u8]) -> Result<bool, Error> {
        self.hammersbald.may_have_key(key)
    }

    fn forget(&mut self, key: &[u8]) -> Result<(), crate::error::Error> {
        self.hammersbald.forget(key)
    }

    fn iter(&self) -> HammersbaldIterator {
        self.hammersbald.iter()
    }
}

#[cfg(test)]
mod test {
    ///! Example use and test

    extern crate hex;

    use bitcoin::{Block, BlockHeader, Network, Transaction};
    use bitcoin::blockdata::constants::genesis_block;

    use transient;
    use super::*;
    use bitcoin::consensus::deserialize;

    #[test]
    pub fn bitcoin_test() {
        // create a transient hammersbald
        let db = transient(1).unwrap();
        // promote to a bitcoin adapter
        let mut bdb = BitcoinAdaptor::new(db);

        // example transaction
        let tx = deserialize::<Transaction> (hex::decode("02000000000101ed30ca30ee83f13579da294e15c9d339b35d33c5e76d2fda68990107d30ff00700000000006db7b08002360b0000000000001600148154619cb0e7513fcdb1eb90cc9f86f3793b9d8ec382ff000000000022002027a5000c7917f785d8fc6e5a55adfca8717ecb973ebb7743849ff956d896a7ed04004730440220503890e657773607fb05c9ef4c4e73b0ab847497ee67b3b8cefb3688a73333180220066db0ca943a5932f309ac9d4f191300711a5fc206d7c3babd85f025eac30bca01473044022055f05c3072dfd389104af1f5ccd56fb5433efc602694f1f384aab703c77ac78002203c1133981d66dc48183e72a19cc0974b93002d35ad7d6ee4278d46b4e96f871a0147522102989711912d88acf5a4a18081104f99c2f8680a7de23f829f28db31fdb45b7a7a2102f0406fa1b49a9bb10c191fd83e2359867ecdace5ea990ce63d11478ed5877f1852ae81534220").unwrap().as_slice()).unwrap();

        // store the transaction without associating a key
        let txref = bdb.put_object(&tx).unwrap();
        // retrieve by direct reference
        let (_, tx2) = bdb.get_object::<Transaction>(txref).unwrap();
        assert_eq!(tx, tx2);

        // store the transaction with its hash as key
        let txref2 = bdb.put_object_by_hash::<Txid, _>(&tx).unwrap();
        // retrieve by hash
        if let Some((pref, tx3)) = bdb.get_object_by_hash::<_, Transaction>(tx.txid()).unwrap() {
            assert_eq!(pref, txref2);
            assert_eq!(tx3, tx);
        } else {
            panic!("can not find tx");
        }

        let genesis = genesis_block(Network::Bitcoin);
        // store the genesist block header
        bdb.put_object_by_hash(&genesis.header).unwrap();
        // find it
        if let Some((_, block)) = bdb.get_object_by_hash::<_, BlockHeader>(genesis.block_hash()).unwrap() {
            assert_eq!(block, genesis.header);
        } else {
            panic!("can not find genesis block header");
        }

        // store the genesist block
        bdb.put_object_by_hash(&genesis).unwrap();
        // find it
        if let Some((_, block)) = bdb.get_object_by_hash::<_, Block>(genesis.block_hash()).unwrap() {
            assert_eq!(block, genesis);
        } else {
            panic!("can not find genesis block");
        }
    }
}