chainseeker_server/
rest.rs

1use serde::{Serialize, Deserialize};
2use bitcoin::hashes::hex::ToHex;
3use bitcoin::{Script, TxIn, TxOut, Address, Network, AddressType, Transaction};
4
5use super::*;
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct RestStatus {
9    pub blocks: i32,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct RestScriptSig {
14    pub asm: String,
15    pub hex: String,
16}
17
18impl RestScriptSig {
19    pub fn new(script: &Script) -> Self {
20        Self {
21            asm: script.asm(),
22            hex: hex::encode(script.as_bytes()),
23        }
24    }
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct RestVin {
30    pub txid: String,
31    pub vout: u32,
32    pub script_sig: RestScriptSig,
33    pub txinwitness: Vec<String>,
34    pub sequence: u32,
35    pub value: u64,
36    pub address: Option<String>,
37}
38
39impl RestVin {
40    pub fn new(txin: &TxIn, previous_txout: &Option<TxOut>, config: &Config) -> Self {
41        Self {
42            txid: txin.previous_output.txid.to_string(),
43            vout: txin.previous_output.vout,
44            script_sig: RestScriptSig::new(&txin.script_sig),
45            txinwitness: txin.witness.iter().map(hex::encode).collect(),
46            sequence: txin.sequence,
47            value: match previous_txout {
48                Some(pt) => pt.value,
49                None => 0,
50            },
51            address: match previous_txout {
52                Some(previous_txout) => script_to_address_string(&previous_txout.script_pubkey, config),
53                None => None,
54            },
55        }
56    }
57}
58
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct RestScriptPubKey {
61    pub asm: String,
62    pub hex: String,
63    pub r#type: String,
64    pub address: Option<String>,
65}
66
67impl RestScriptPubKey {
68    pub fn new(script_pubkey: &Script, config: &Config) -> Self {
69        let address = Address::from_script(&script_pubkey, Network::Bitcoin /* any */);
70        let address_str = script_to_address_string(&script_pubkey, config);
71        Self {
72            asm: script_pubkey.asm(),
73            hex: hex::encode(script_pubkey.as_bytes()),
74            r#type: address.map_or("unknown", |address| address.address_type().map_or("unknown", |address_type| {
75                match address_type {
76                    AddressType::P2pkh  => "pubkeyhash",
77                    AddressType::P2sh   => "scripthash",
78                    AddressType::P2wpkh => "witnesspubkeyhash",
79                    AddressType::P2wsh  => "witnessscripthash",
80                }
81            })).to_string(),
82            address: address_str,
83        }
84    }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct RestUtxo {
90    pub txid: String,
91    pub vout: u32,
92    pub script_pub_key: RestScriptPubKey,
93    pub value: u64,
94}
95
96impl RestUtxo {
97    pub fn new(value: &UtxoServerValue, tx: &Transaction, config: &Config) -> Self {
98        let previous_output = &tx.output[value.vout as usize];
99        Self {
100            txid: value.txid.to_string(),
101            vout: value.vout,
102            script_pub_key: RestScriptPubKey::new(&previous_output.script_pubkey, config),
103            value: previous_output.value,
104        }
105    }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
109#[serde(rename_all = "camelCase")]
110pub struct RestVout {
111    pub value: u64,
112    pub n: usize,
113    pub script_pub_key: RestScriptPubKey,
114}
115
116impl RestVout {
117    pub fn new(txout: &TxOut, n: usize, config: &Config) -> Self {
118        Self {
119            value: txout.value,
120            n,
121            script_pub_key: RestScriptPubKey::new(&txout.script_pubkey, config),
122        }
123    }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct RestTx {
129    pub confirmed_height: Option<u32>,
130    pub hex: String,
131    pub txid: String,
132    pub hash: String,
133    pub size: usize,
134    pub vsize: usize,
135    pub weight: usize,
136    pub version: i32,
137    pub locktime: u32,
138    pub vin: Vec<RestVin>,
139    pub vout: Vec<RestVout>,
140    pub fee: i64,
141    //counterparty: ,
142}
143
144#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
145pub struct RestBlockHeader {
146    pub height: u32,
147    pub header: String,
148    pub hash: String,
149    pub version: i32,
150    pub previousblockhash: String,
151    pub merkleroot: String,
152    pub time: u32,
153    pub bits: String,
154    pub difficulty: f64,
155    pub nonce: u32,
156    pub size: u32,
157    pub strippedsize: u32,
158    pub weight: u32,
159    pub ntxs: usize,
160}
161
162impl RestBlockHeader {
163    pub fn from_block_content(block_content: &BlockContentDBValue, config: &Config) -> Self {
164        let block_header = &block_content.block_header;
165        Self {
166            height           : block_content.height,
167            header           : hex::encode(consensus_encode(&block_header)),
168            hash             : block_header.block_hash().to_string(),
169            version          : block_header.version,
170            previousblockhash: block_header.prev_blockhash.to_string(),
171            merkleroot       : block_header.merkle_root.to_string(),
172            time             : block_header.time,
173            bits             : format!("{:x}", block_header.bits),
174            difficulty       : get_difficulty(block_header, config),
175            nonce            : block_header.nonce,
176            size             : block_content.size,
177            strippedsize     : block_content.strippedsize,
178            weight           : block_content.weight,
179            ntxs             : block_content.txids.len(),
180        }
181    }
182}
183
184#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
185pub struct RestBlockWithTxids {
186    pub height: u32,
187    pub header: String,
188    pub hash: String,
189    pub version: i32,
190    pub previousblockhash: String,
191    pub merkleroot: String,
192    pub time: u32,
193    pub bits: String,
194    pub difficulty: f64,
195    pub nonce: u32,
196    pub size: u32,
197    pub strippedsize: u32,
198    pub weight: u32,
199    pub txids: Vec<String>,
200}
201
202impl RestBlockWithTxids {
203    pub fn from_block_content(block_content: &BlockContentDBValue, config: &Config) -> Self {
204        let rest_block_header = RestBlockHeader::from_block_content(block_content, config);
205        Self {
206            height           : rest_block_header.height,
207            header           : rest_block_header.header,
208            hash             : rest_block_header.hash,
209            version          : rest_block_header.version,
210            previousblockhash: rest_block_header.previousblockhash,
211            merkleroot       : rest_block_header.merkleroot,
212            time             : rest_block_header.time,
213            bits             : rest_block_header.bits,
214            difficulty       : rest_block_header.difficulty,
215            nonce            : rest_block_header.nonce,
216            size             : rest_block_header.size,
217            strippedsize     : rest_block_header.strippedsize,
218            weight           : rest_block_header.weight,
219            txids            : block_content.txids.iter().map(|txid| txid.to_hex()).collect::<Vec<String>>(),
220        }
221    }
222}
223
224#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
225pub struct RestBlockWithTxs {
226    pub height: u32,
227    pub header: String,
228    pub hash: String,
229    pub version: i32,
230    pub previousblockhash: String,
231    pub merkleroot: String,
232    pub time: u32,
233    pub bits: String,
234    pub difficulty: f64,
235    pub nonce: u32,
236    pub size: u32,
237    pub strippedsize: u32,
238    pub weight: u32,
239    pub txs: Vec<RestTx>,
240}
241
242impl RestBlockWithTxs {
243    pub fn from_block_content(tx_db: &TxDB, block_content: &BlockContentDBValue, config: &Config) -> Self {
244        let rest_block_header = RestBlockHeader::from_block_content(block_content, config);
245        // TODO: waiting upstream fix: https://github.com/rust-rocksdb/rust-rocksdb/issues/536
246        //let txs = tx_db.multi_get_as_rest(block_content.txids.clone());
247        let txs = block_content.txids.iter().map(|txid| {
248            tx_db.get_as_rest(txid, config).unwrap()
249        }).collect::<Vec<RestTx>>();
250        Self {
251            height           : rest_block_header.height,
252            header           : rest_block_header.header,
253            hash             : rest_block_header.hash,
254            version          : rest_block_header.version,
255            previousblockhash: rest_block_header.previousblockhash,
256            merkleroot       : rest_block_header.merkleroot,
257            time             : rest_block_header.time,
258            bits             : rest_block_header.bits,
259            difficulty       : rest_block_header.difficulty,
260            nonce            : rest_block_header.nonce,
261            size             : rest_block_header.size,
262            strippedsize     : rest_block_header.strippedsize,
263            weight           : rest_block_header.weight,
264            txs,
265        }
266    }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct RestRichListEntry {
272    pub script_pub_key: RestScriptPubKey,
273    pub value: u64,
274}
275
276#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
277pub struct RestBlockSummary {
278    pub hash        : String,
279    pub time        : u32,
280    pub nonce       : u32,
281    pub size        : u32,
282    pub strippedsize: u32,
283    pub weight      : u32,
284    pub txcount     : usize,
285}
286
287impl RestBlockSummary {
288    pub fn new(block: &BlockContentDBValue) -> Self {
289        Self {
290            hash        : block.block_header.block_hash().to_string(),
291            time        : block.block_header.time,
292            nonce       : block.block_header.nonce,
293            size        : block.size,
294            strippedsize: block.strippedsize,
295            weight      : block.weight,
296            txcount     : block.txids.len(),
297        }
298    }
299}
300
301#[cfg(test)]
302mod tests {
303    use super::*;
304    #[test]
305    fn rest() {
306        let tx_db = TxDB::new("test/rest", true);
307        let regtest_blocks = fixtures::regtest_blocks();
308        for (height, block) in regtest_blocks.iter().enumerate() {
309            for tx in block.txdata.iter() {
310                tx_db.put_tx(tx, Some(height as u32)).unwrap();
311            }
312        }
313        let config = config_example("rbtc");
314        let block_rest = RestBlockWithTxs::from_block_content(&tx_db, &BlockContentDBValue::new(102, &regtest_blocks[102]), &config);
315        let block_rest_json = serde_json::to_string(&block_rest).unwrap();
316        println!("{}", block_rest_json);
317        let block_json = r#"{"height":102,"header":"00000020f4a34bc39e46acbf6ad1cd786d978718b4ef94002eb080e86f383b77798b8b1e197bc47e4d72c8cc02c78a6c89a45db03f9906c1a60448bbeecf7b84566fc66eca41e560ffff7f2001000000","hash":"25263a195c89fae46d08558b1b501617aac630a6854000e19eb7ada6c39e6e0b","version":536870912,"previousblockhash":"1e8b8b79773b386fe880b02e0094efb41887976d78cdd16abfac469ec34ba3f4","merkleroot":"6ec66f56847bcfeebb4804a6c106993fb05da4896c8ac702ccc8724d7ec47b19","time":1625637322,"bits":"207fffff","difficulty":4.6565423739069247e-10,"nonce":1,"size":842,"strippedsize":481,"weight":2285,"txs":[{"confirmedHeight":102,"hex":"020000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff0401660101ffffffff02547a062a0100000016001497033ca70d45fe6d49310859e132a9df98f976250000000000000000266a24aa21a9edaaed4e9155661fb1a2bfc87c1458ab0eebc25bf8f0b8126ccc9214c407ebd34f0120000000000000000000000000000000000000000000000000000000000000000000000000","txid":"b903046c3fb719a2a404e3b749609414b4f914b9cbf2eadf289c8ed2120aaebc","hash":"cb01519e3eee67593f7f0eb955a82a3aa6fc1c83364c50e7c6891b14320d88c7","size":169,"vsize":142,"weight":568,"version":2,"locktime":0,"vin":[{"txid":"0000000000000000000000000000000000000000000000000000000000000000","vout":4294967295,"scriptSig":{"asm":"OP_PUSHBYTES_1 66 OP_PUSHBYTES_1 01","hex":"01660101"},"txinwitness":["0000000000000000000000000000000000000000000000000000000000000000"],"sequence":4294967295,"value":0,"address":null}],"vout":[{"value":5000034900,"n":0,"scriptPubKey":{"asm":"OP_0 OP_PUSHBYTES_20 97033ca70d45fe6d49310859e132a9df98f97625","hex":"001497033ca70d45fe6d49310859e132a9df98f97625","type":"witnesspubkeyhash","address":"bcrt1qjupnefcdghlx6jf3ppv7zv4fm7v0ja39dzzwvd"}},{"value":0,"n":1,"scriptPubKey":{"asm":"OP_RETURN OP_PUSHBYTES_36 aa21a9edaaed4e9155661fb1a2bfc87c1458ab0eebc25bf8f0b8126ccc9214c407ebd34f","hex":"6a24aa21a9edaaed4e9155661fb1a2bfc87c1458ab0eebc25bf8f0b8126ccc9214c407ebd34f","type":"unknown","address":null}}],"fee":-5000034900},{"confirmedHeight":102,"hex":"02000000000101592f96fe043aaa22cdcb6f6e710946aa0af25dff4536759f6873965555a6660c0000000000fdffffff02ecd90f2401000000160014629ef06211f8e75e223b6338228a025bd15b0db900e1f50500000000160014f0e9ede24bceb0c16fdce952279d8094dc6b3f5802473044022057f0dde0e8d094034f47a136ae22a433e0216d5b1cfa3a03d8d259660148744702204377d2dbf3e442128c03097af4d4fa56b52b0cc7d9c24511b99d45a47e8df2d7012102b934d90a0aa5e0b73e04d00df3a8633ee16d24dad9ab21697f1dc5feb43fdbed65000000","txid":"e6a6a7db6faadbfc0815fd3a78c3b5d15cb733dde8aca0e911b6f565ea24ee73","hash":"4fdf235851b72bb7ddc8f6d86e43797bb42e3601ee1eb1e8d8a0e157b3c81af6","size":222,"vsize":141,"weight":561,"version":2,"locktime":101,"vin":[{"txid":"0c66a655559673689f753645ff5df20aaa4609716e6fcbcd22aa3a04fe962f59","vout":0,"scriptSig":{"asm":"","hex":""},"txinwitness":["3044022057f0dde0e8d094034f47a136ae22a433e0216d5b1cfa3a03d8d259660148744702204377d2dbf3e442128c03097af4d4fa56b52b0cc7d9c24511b99d45a47e8df2d701","02b934d90a0aa5e0b73e04d00df3a8633ee16d24dad9ab21697f1dc5feb43fdbed"],"sequence":4294967293,"value":5000000000,"address":"bcrt1qjupnefcdghlx6jf3ppv7zv4fm7v0ja39dzzwvd"}],"vout":[{"value":4899985900,"n":0,"scriptPubKey":{"asm":"OP_0 OP_PUSHBYTES_20 629ef06211f8e75e223b6338228a025bd15b0db9","hex":"0014629ef06211f8e75e223b6338228a025bd15b0db9","type":"witnesspubkeyhash","address":"bcrt1qv200qcs3lrn4ug3mvvuz9zszt0g4krde3uqyzy"}},{"value":100000000,"n":1,"scriptPubKey":{"asm":"OP_0 OP_PUSHBYTES_20 f0e9ede24bceb0c16fdce952279d8094dc6b3f58","hex":"0014f0e9ede24bceb0c16fdce952279d8094dc6b3f58","type":"witnesspubkeyhash","address":"bcrt1q7r57mcjte6cvzm7ua9fz08vqjnwxk06c2v6jdv"}}],"fee":14100},{"confirmedHeight":102,"hex":"0200000000010273ee24ea65f5b611e9a0ace8dd33b75cd1b5c3783afd1508fcdbaa6fdba7a6e60000000000fdffffff73ee24ea65f5b611e9a0ace8dd33b75cd1b5c3783afd1508fcdbaa6fdba7a6e60100000000fdffffff02001110240100000016001413bb0bcb776f3e15fa8800877552044d4db27b96ac58f50500000000160014261b6555a3cb5d3593c0275ff46f31c36e42a4c70247304402207a89cf2b2d7620ace221894746c4a72f5fd5dd5bbe9e56018b182eaf046e9766022050a14136c20402b281c244a918856b9e866b5fc77182334dd6242993e2144b7d0121034968df50370db27d51b294cba769ec47e0accb3a582c312549303c555ca834d102473044022058a1f0d9b8bde83c28954a1cfac6a3f43f8f8b53529df47d0ef2f7d66f8f42bb022026993b9ba88bedff19ec3f70d32bab177d35e0570f8c86a9b2eac5b359189c1d012103d0d5b793a8a23ff2e92b6204c15f72fd51765ad6c01f6ebd9e712adee11bda4065000000","txid":"29704e049abbacf3222cd8af9654afe6c7c91d5d9f2faa5d2f0f3cd5de3a812d","hash":"fb3551865d89fae25b8bcc5a02da25ad91d6fe1c0d4fd391b4c7b1b69e9ece63","size":370,"vsize":208,"weight":832,"version":2,"locktime":101,"vin":[{"txid":"e6a6a7db6faadbfc0815fd3a78c3b5d15cb733dde8aca0e911b6f565ea24ee73","vout":0,"scriptSig":{"asm":"","hex":""},"txinwitness":["304402207a89cf2b2d7620ace221894746c4a72f5fd5dd5bbe9e56018b182eaf046e9766022050a14136c20402b281c244a918856b9e866b5fc77182334dd6242993e2144b7d01","034968df50370db27d51b294cba769ec47e0accb3a582c312549303c555ca834d1"],"sequence":4294967293,"value":4899985900,"address":"bcrt1qv200qcs3lrn4ug3mvvuz9zszt0g4krde3uqyzy"},{"txid":"e6a6a7db6faadbfc0815fd3a78c3b5d15cb733dde8aca0e911b6f565ea24ee73","vout":1,"scriptSig":{"asm":"","hex":""},"txinwitness":["3044022058a1f0d9b8bde83c28954a1cfac6a3f43f8f8b53529df47d0ef2f7d66f8f42bb022026993b9ba88bedff19ec3f70d32bab177d35e0570f8c86a9b2eac5b359189c1d01","03d0d5b793a8a23ff2e92b6204c15f72fd51765ad6c01f6ebd9e712adee11bda40"],"sequence":4294967293,"value":100000000,"address":"bcrt1q7r57mcjte6cvzm7ua9fz08vqjnwxk06c2v6jdv"}],"vout":[{"value":4900000000,"n":0,"scriptPubKey":{"asm":"OP_0 OP_PUSHBYTES_20 13bb0bcb776f3e15fa8800877552044d4db27b96","hex":"001413bb0bcb776f3e15fa8800877552044d4db27b96","type":"witnesspubkeyhash","address":"bcrt1qzwashjmhdulpt75gqzrh25syf4xmy7uk6clm0p"}},{"value":99965100,"n":1,"scriptPubKey":{"asm":"OP_0 OP_PUSHBYTES_20 261b6555a3cb5d3593c0275ff46f31c36e42a4c7","hex":"0014261b6555a3cb5d3593c0275ff46f31c36e42a4c7","type":"witnesspubkeyhash","address":"bcrt1qycdk24dredwnty7qya0lgme3cdhy9fx83qc9wd"}}],"fee":20800}]}"#;
318        assert_eq!(block_rest_json, block_json);
319    }
320}