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}