1use miniscript::bitcoin::{
3 blockdata::block::Header as BlockHeader,
4 consensus::encode::{deserialize, Decodable, Encodable},
5 hashes::{hash_newtype, sha256, Hash},
6 OutPoint, Script, Txid,
7};
8use miniscript::serde::{Deserialize, Serialize};
9use std::convert::TryFrom;
10
11macro_rules! impl_consensus_encoding {
12 ($thing:ident, $($field:ident),+) => (
13 impl Encodable for $thing {
14 #[inline]
15 fn consensus_encode<S: miniscript::bitcoin::io::Write + ?Sized>(
16 &self,
17 s: &mut S,
18 ) -> Result<usize, miniscript::bitcoin::io::Error> {
19 let mut len = 0;
20 $(len += self.$field.consensus_encode(s)?;)+
21 Ok(len)
22 }
23 }
24
25 impl Decodable for $thing {
26 #[inline]
27 fn consensus_decode<D: miniscript::bitcoin::io::Read + ?Sized>(
28 d: &mut D,
29 ) -> Result<$thing, miniscript::bitcoin::consensus::encode::Error> {
30 Ok($thing {
31 $($field: Decodable::consensus_decode(d)?),+
32 })
33 }
34 }
35 );
36}
37
38pub const HASH_PREFIX_LEN: usize = 8;
39const HEIGHT_SIZE: usize = 4;
40
41pub(crate) type HashPrefix = [u8; HASH_PREFIX_LEN];
42pub(crate) type SerializedHashPrefixRow = [u8; HASH_PREFIX_ROW_SIZE];
43type Height = u32;
44pub(crate) type SerBlock = Vec<u8>;
45
46#[derive(Debug, Serialize, Deserialize, PartialEq)]
47pub(crate) struct HashPrefixRow {
48 prefix: HashPrefix,
49 height: Height, }
51
52pub const HASH_PREFIX_ROW_SIZE: usize = HASH_PREFIX_LEN + HEIGHT_SIZE;
53
54impl HashPrefixRow {
55 pub(crate) fn to_db_row(&self) -> SerializedHashPrefixRow {
56 let mut row = [0; HASH_PREFIX_ROW_SIZE];
57 let len = self
58 .consensus_encode(&mut (&mut row as &mut [u8]))
59 .expect("in-memory writers don't error");
60 debug_assert_eq!(len, HASH_PREFIX_ROW_SIZE);
61 row
62 }
63
64 pub(crate) fn from_db_row(row: SerializedHashPrefixRow) -> Self {
65 deserialize(&row).expect("bad HashPrefixRow")
66 }
67
68 pub fn height(&self) -> usize {
69 usize::try_from(self.height).expect("invalid height")
70 }
71}
72
73impl_consensus_encoding!(HashPrefixRow, prefix, height);
74
75hash_newtype! {
76 #[hash_newtype(backward)]
78 pub struct ScriptHash(sha256::Hash);
79}
80
81impl ScriptHash {
82 pub fn new(script: &Script) -> Self {
83 ScriptHash::hash(script.as_bytes())
84 }
85
86 fn prefix(&self) -> HashPrefix {
87 let mut prefix = HashPrefix::default();
88 prefix.copy_from_slice(&self.0[..HASH_PREFIX_LEN]);
89 prefix
90 }
91}
92
93pub(crate) struct ScriptHashRow;
94
95impl ScriptHashRow {
96 pub(crate) fn scan_prefix(scripthash: ScriptHash) -> HashPrefix {
97 scripthash.0[..HASH_PREFIX_LEN].try_into().unwrap()
98 }
99
100 pub(crate) fn row(scripthash: ScriptHash, height: usize) -> HashPrefixRow {
101 HashPrefixRow {
102 prefix: scripthash.prefix(),
103 height: Height::try_from(height).expect("invalid height"),
104 }
105 }
106}
107
108hash_newtype! {
111 pub struct StatusHash(sha256::Hash);
113}
114
115fn spending_prefix(prev: OutPoint) -> HashPrefix {
118 let txid_prefix = HashPrefix::try_from(&prev.txid[..HASH_PREFIX_LEN]).unwrap();
119 let value = u64::from_be_bytes(txid_prefix);
120 let value = value.wrapping_add(prev.vout.into());
121 value.to_be_bytes()
122}
123
124pub(crate) struct SpendingPrefixRow;
125
126impl SpendingPrefixRow {
127 pub(crate) fn scan_prefix(outpoint: OutPoint) -> HashPrefix {
128 spending_prefix(outpoint)
129 }
130
131 pub(crate) fn row(outpoint: OutPoint, height: usize) -> HashPrefixRow {
132 HashPrefixRow {
133 prefix: spending_prefix(outpoint),
134 height: Height::try_from(height).expect("invalid height"),
135 }
136 }
137}
138
139fn txid_prefix(txid: &Txid) -> HashPrefix {
142 let mut prefix = [0u8; HASH_PREFIX_LEN];
143 prefix.copy_from_slice(&txid[..HASH_PREFIX_LEN]);
144 prefix
145}
146
147pub(crate) struct TxidRow;
148
149impl TxidRow {
150 pub(crate) fn scan_prefix(txid: Txid) -> HashPrefix {
151 txid_prefix(&txid)
152 }
153
154 pub(crate) fn row(txid: Txid, height: usize) -> HashPrefixRow {
155 HashPrefixRow {
156 prefix: txid_prefix(&txid),
157 height: Height::try_from(height).expect("invalid height"),
158 }
159 }
160}
161
162pub(crate) type SerializedHeaderRow = [u8; HEADER_ROW_SIZE];
165
166#[derive(Debug, Serialize, Deserialize)]
167pub(crate) struct HeaderRow {
168 pub(crate) header: BlockHeader,
169}
170
171pub const HEADER_ROW_SIZE: usize = 80;
172
173impl_consensus_encoding!(HeaderRow, header);
174
175impl HeaderRow {
176 pub(crate) fn new(header: BlockHeader) -> Self {
177 Self { header }
178 }
179
180 pub(crate) fn to_db_row(&self) -> SerializedHeaderRow {
181 let mut row = [0; HEADER_ROW_SIZE];
182 let len = self
183 .consensus_encode(&mut (&mut row as &mut [u8]))
184 .expect("in-memory writers don't error");
185 debug_assert_eq!(len, HEADER_ROW_SIZE);
186 row
187 }
188
189 pub(crate) fn from_db_row(row: SerializedHeaderRow) -> Self {
190 deserialize(&row).expect("bad HeaderRow")
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use crate::electrum::types::{
197 spending_prefix, HashPrefixRow, ScriptHash, ScriptHashRow, TxidRow,
198 };
199 use hex_lit::hex;
200 use miniscript::bitcoin::{Address, OutPoint, Txid};
201 use serde_json::{from_str, json};
202
203 use std::str::FromStr;
204
205 #[test]
206 fn test_scripthash_serde() {
207 let hex = "\"4b3d912c1523ece4615e91bf0d27381ca72169dbf6b1c2ffcc9f92381d4984a3\"";
208 let scripthash: ScriptHash = from_str(hex).unwrap();
209 assert_eq!(format!("\"{}\"", scripthash), hex);
210 assert_eq!(json!(scripthash).to_string(), hex);
211 }
212
213 #[test]
214 fn test_scripthash_row() {
215 let hex = "\"4b3d912c1523ece4615e91bf0d27381ca72169dbf6b1c2ffcc9f92381d4984a3\"";
216 let scripthash: ScriptHash = from_str(hex).unwrap();
217 let row1 = ScriptHashRow::row(scripthash, 123456);
218 let db_row = row1.to_db_row();
219 assert_eq!(db_row, hex!("a384491d38929fcc40e20100"));
220 let row2 = HashPrefixRow::from_db_row(db_row);
221 assert_eq!(row1, row2);
222 }
223
224 #[test]
225 fn test_scripthash() {
226 let addr = Address::from_str("1KVNjD3AAnQ3gTMqoTKcWFeqSFujq9gTBT")
227 .unwrap()
228 .assume_checked();
229 let scripthash = ScriptHash::new(&addr.script_pubkey());
230 assert_eq!(
231 scripthash,
232 "00dfb264221d07712a144bda338e89237d1abd2db4086057573895ea2659766a"
233 .parse()
234 .unwrap()
235 );
236 }
237
238 #[test]
239 fn test_txid1_prefix() {
240 let hex = "d5d27987d2a3dfc724e359870c6644b40e497bdc0589a033220fe15429d88599";
242 let txid = Txid::from_str(hex).unwrap();
243
244 let row1 = TxidRow::row(txid, 91812);
245 let row2 = TxidRow::row(txid, 91842);
246
247 assert_eq!(row1.to_db_row(), hex!("9985d82954e10f22a4660100"));
248 assert_eq!(row2.to_db_row(), hex!("9985d82954e10f22c2660100"));
249 }
250
251 #[test]
252 fn test_txid2_prefix() {
253 let hex = "e3bf3d07d4b0375638d5f1db5255fe07ba2c4cb067cd81b84ee974b6585fb468";
255 let txid = Txid::from_str(hex).unwrap();
256
257 let row1 = TxidRow::row(txid, 91722);
258 let row2 = TxidRow::row(txid, 91880);
259
260 assert_eq!(row1.to_db_row(), hex!("68b45f58b674e94e4a660100"));
262 assert_eq!(row2.to_db_row(), hex!("68b45f58b674e94ee8660100"));
263 }
264
265 #[test]
266 fn test_spending_prefix() {
267 let txid = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
268 .parse()
269 .unwrap();
270
271 assert_eq!(
272 spending_prefix(OutPoint { txid, vout: 0 }),
273 [31, 30, 29, 28, 27, 26, 25, 24]
274 );
275 assert_eq!(
276 spending_prefix(OutPoint { txid, vout: 10 }),
277 [31, 30, 29, 28, 27, 26, 25, 34]
278 );
279 assert_eq!(
280 spending_prefix(OutPoint { txid, vout: 255 }),
281 [31, 30, 29, 28, 27, 26, 26, 23]
282 );
283 assert_eq!(
284 spending_prefix(OutPoint { txid, vout: 256 }),
285 [31, 30, 29, 28, 27, 26, 26, 24]
286 );
287 }
288}