bitcoind_cache/
lib.rs

1pub mod store;
2pub mod utils;
3
4use bitcoin::blockdata::constants::genesis_block;
5use bitcoin::consensus::encode::{serialize, serialize_hex};
6use bitcoin::consensus::{self, deserialize};
7use bitcoin::hashes::hex::{FromHex, ToHex};
8use bitcoin::util::uint::Uint256;
9use bitcoin::{Block, BlockHash, BlockHeader, Network};
10
11use store::{AnyStore, Store, StoreError};
12
13#[derive(Debug)]
14pub enum BitcoindCacheError {
15    Store(StoreError),
16}
17
18impl From<StoreError> for BitcoindCacheError {
19    fn from(store_error: StoreError) -> BitcoindCacheError {
20        BitcoindCacheError::Store(store_error)
21    }
22}
23
24pub type BitcoindCacheResult<T> = Result<T, BitcoindCacheError>;
25
26#[derive(Clone)]
27pub struct BitcoindCache {
28    pub store: AnyStore,
29    pub network: Network,
30}
31
32impl BitcoindCache {
33    pub fn new(network: Network, store: AnyStore) -> Self {
34        Self { store, network }
35    }
36
37    pub fn best_block_hash_path(&self) -> String {
38        "best-block-hash".to_string()
39    }
40
41    pub fn best_block_height_path(&self) -> String {
42        "best-block-height".to_string()
43    }
44
45    pub fn header_path(&self, block_hash: String) -> String {
46        format!("{}.header", block_hash)
47    }
48
49    pub fn block_path(&self, block_hash: String) -> String {
50        format!("{}.block", block_hash)
51    }
52
53    pub async fn put_best_block_hash(&self, block_hash: &BlockHash) -> BitcoindCacheResult<()> {
54        Ok(self
55            .store
56            .put_object(self.best_block_hash_path(), &serialize(block_hash))
57            .await?)
58    }
59
60    pub async fn put_best_block_height(&self, block_height: u32) -> BitcoindCacheResult<()> {
61        Ok(self
62            .store
63            .put_object(
64                self.best_block_height_path(),
65                block_height.to_string().as_bytes(),
66            )
67            .await?)
68    }
69
70    pub async fn put_block(&self, block: &Block) -> BitcoindCacheResult<()> {
71        let block_hash_hex = block.block_hash().to_hex();
72
73        Ok(self
74            .store
75            .put_object(self.block_path(block_hash_hex), &serialize(block))
76            .await?)
77    }
78
79    pub async fn put_header(
80        &self,
81        header: &BlockHeader,
82        height: u32,
83        chainwork: Uint256,
84    ) -> BitcoindCacheResult<()> {
85        let block_hash_hex = header.block_hash().to_hex();
86        let chainwork_hex = utils::hex_str(&consensus::serialize(&chainwork));
87        let header_data = format!("{},{},{}", serialize_hex(header), height, chainwork_hex);
88
89        Ok(self
90            .store
91            .put_object(self.header_path(block_hash_hex), header_data.as_bytes())
92            .await?)
93    }
94
95    pub async fn get_header_by_hash(
96        &self,
97        hash: &BlockHash,
98    ) -> BitcoindCacheResult<Option<(BlockHeader, u32, Uint256)>> {
99        let header = self
100            .store
101            .get_object(self.header_path(hash.to_hex()))
102            .await?;
103
104        Ok(header.map(|header_bytes| {
105            let header_data_string = String::from_utf8(header_bytes).unwrap();
106            let header_parts: Vec<&str> = header_data_string.split(',').collect();
107            let header: BlockHeader =
108                deserialize(&Vec::<u8>::from_hex(header_parts[0]).unwrap()).unwrap();
109            let height: u32 = header_parts[1].to_string().parse().unwrap();
110            let chainwork: Uint256 = deserialize(&utils::to_vec(header_parts[2]).unwrap()).unwrap();
111            (header, height, chainwork)
112        }))
113    }
114
115    pub async fn get_block_by_hash(&self, hash: &BlockHash) -> BitcoindCacheResult<Option<Block>> {
116        let block = self
117            .store
118            .get_object(self.block_path(hash.to_hex()))
119            .await?;
120
121        Ok(block.map(|serialized_block| {
122            deserialize(&serialized_block).expect("data to be encoded correctly")
123        }))
124    }
125
126    pub async fn get_best_block_hash(&self) -> BitcoindCacheResult<Option<BlockHash>> {
127        let block_hash = self.store.get_object(self.best_block_hash_path()).await?;
128
129        Ok(block_hash.map(|block_hash_bytes| {
130            deserialize(&block_hash_bytes).expect("data to be encoded correctly")
131        }))
132    }
133
134    pub async fn get_best_block_height(&self) -> BitcoindCacheResult<Option<u32>> {
135        let height = self.store.get_object(self.best_block_height_path()).await?;
136
137        Ok(height.map(|height_bytes| String::from_utf8(height_bytes).unwrap().parse().unwrap()))
138    }
139
140    pub async fn get_cached_best_block(&self) -> BitcoindCacheResult<(BlockHash, u32)> {
141        let best_hash = self.get_best_block_hash().await?;
142        let best_height = self.get_best_block_height().await?;
143
144        if best_hash.is_none() || best_height.is_none() {
145            let genesis_block = genesis_block(self.network);
146            let genesis_hash = genesis_block.header.block_hash();
147            self.block_connected(&genesis_block, 0, Uint256::from_u64(0).unwrap())
148                .await?;
149            Ok((genesis_hash, 0))
150        } else {
151            Ok((best_hash.unwrap(), best_height.unwrap()))
152        }
153    }
154
155    pub async fn block_connected(
156        &self,
157        block: &Block,
158        height: u32,
159        chainwork: Uint256,
160    ) -> BitcoindCacheResult<()> {
161        self.put_block(block).await?;
162        self.put_header(&block.header, height, chainwork).await?;
163        self.put_best_block_hash(&block.block_hash()).await?;
164        self.put_best_block_height(height).await?;
165
166        Ok(())
167    }
168}