1#[cfg(feature="softforks")]
9use std::collections::HashMap;
10pub use bytes;
11pub use serde;
12pub use reqwest;
13pub use bitcoin;
14use serde::{Deserialize, Serialize};
15use bitcoin::hash_types::{BlockHash, Txid};
16use bitcoin::blockdata::block::{Block, BlockHeader};
17use bitcoin::blockdata::transaction::Transaction;
18use bitcoin::consensus::Decodable;
19
20pub const DEFAULT_ENDPOINT: &str = "http://localhost:8332/rest";
21
22#[derive(Debug, Clone, Deserialize, Serialize)]
23pub struct Softfork {
24 #[serde(rename="type")]
25 pub type_: String,
26 pub active: bool,
27 #[serde(default)]
28 pub height: u32,
29}
30
31#[derive(Debug, Clone, Deserialize, Serialize)]
32pub struct ChainInfo {
33 pub chain: String,
34 pub blocks: u32,
35 pub headers: u32,
36 pub bestblockhash: String,
37 pub difficulty: f64,
38 pub mediantime: u32,
39 pub verificationprogress: f64,
40 pub chainwork: String,
41 pub pruned: bool,
42 #[serde(default)]
43 pub pruneheight: u32,
44 #[cfg(feature="softforks")]
45 pub softforks: HashMap<String, Softfork>,
46 pub warnings: String,
47}
48
49#[derive(Debug, Clone, Deserialize, Serialize)]
50#[serde(rename_all = "camelCase")]
51pub struct ScriptPubKey {
52 pub asm: String,
53 pub hex: String,
54 #[serde(default)]
55 pub req_sigs: u32,
56 #[serde(rename="type")]
57 pub type_: String,
58 #[serde(default)]
59 pub addresses: Vec<String>,
60}
61
62#[derive(Debug, Clone, Deserialize, Serialize)]
63#[serde(rename_all = "camelCase")]
64pub struct Utxo {
65 pub height: u32,
66 pub value: f64,
67 pub script_pub_key: ScriptPubKey,
68}
69
70#[derive(Debug, Clone, Deserialize, Serialize)]
71#[serde(rename_all = "camelCase")]
72pub struct UtxoData {
73 pub chain_height: u32,
74 pub chaintip_hash: String,
75 pub bitmap: String,
76 pub utxos: Vec<Utxo>,
77}
78
79#[derive(Debug)]
80pub enum Error {
81 Reqwest(reqwest::Error),
82 BitcoinEncodeError(bitcoin::consensus::encode::Error)
83}
84
85impl From<reqwest::Error> for Error {
86 fn from(err: reqwest::Error) -> Self {
87 Self::Reqwest(err)
88 }
89}
90
91impl From<bitcoin::consensus::encode::Error> for Error {
92 fn from(err: bitcoin::consensus::encode::Error) -> Self {
93 Self::BitcoinEncodeError(err)
94 }
95}
96
97#[derive(Debug, Clone)]
99pub struct Context {
100 endpoint: String,
101 client: reqwest::Client,
102}
103
104pub fn new(endpoint: &str) -> Context {
109 Context {
110 endpoint: endpoint.to_string(),
111 client: reqwest::Client::new(),
112 }
113}
114
115impl Context {
116 pub async fn call_json<T: for<'de> Deserialize<'de>>(&self, path: &str) -> Result<T, reqwest::Error> {
118 let url = format!("{}/{}.json", &self.endpoint, path);
119 let result = self.client.get(url)
120 .send().await?
121 .json::<T>().await?;
122 Ok(result)
123 }
124 pub async fn call_bin(&self, path: &str) -> Result<bytes::Bytes, reqwest::Error> {
126 let url = format!("{}/{}.bin", &self.endpoint, path);
127 let result = self.client.get(url)
128 .send().await?
129 .bytes().await?;
130 Ok(result)
131 }
132 pub async fn call_hex(&self, path: &str) -> Result<String, reqwest::Error> {
134 let url = format!("{}/{}.hex", &self.endpoint, path);
135 let mut result = self.client.get(url)
136 .send().await?
137 .text().await?;
138 result.pop();
140 Ok(result)
141 }
142 pub async fn tx(&self, txhash: &Txid) -> Result<Transaction, Error> {
144 let result = self.call_bin(&["tx", &txhash.to_string()].join("/")).await?;
145 Ok(Transaction::consensus_decode(result.as_ref())?)
146 }
147 pub async fn block(&self, blockhash: &BlockHash) -> Result<Block, Error> {
149 let result = self.call_bin(&["block", &blockhash.to_string()].join("/")).await?;
150 Ok(Block::consensus_decode(result.as_ref())?)
151 }
152 pub async fn block_notxdetails(&self, blockhash: &BlockHash) -> Result<BlockHeader, Error> {
154 let result = self.call_bin(&["block", "notxdetails", &blockhash.to_string()].join("/")).await?;
155 Ok(BlockHeader::consensus_decode(result.as_ref())?)
156 }
157 pub async fn headers(&self, count: u32, blockhash: &BlockHash) -> Result<Vec<BlockHeader>, Error> {
159 let result = self.call_bin(&["headers", &count.to_string(), &blockhash.to_string()].join("/")).await?;
160 let mut ret = Vec::new();
161 const BLOCK_HEADER_SIZE: usize = 80usize;
162 let mut offset = 0;
163 while offset < result.len() {
164 ret.push(BlockHeader::consensus_decode(result[offset..(offset+BLOCK_HEADER_SIZE)].as_ref())?);
165 offset += BLOCK_HEADER_SIZE;
166 }
167 Ok(ret)
168 }
169 pub async fn blockhashbyheight(&self, height: u32) -> Result<BlockHash, Error> {
171 let result = self.call_bin(&["blockhashbyheight", &height.to_string()].join("/")).await?;
172 Ok(BlockHash::consensus_decode(result.as_ref())?)
173 }
174 pub async fn chaininfo(&self) -> Result<ChainInfo, Error> {
176 let result: ChainInfo = self.call_json("chaininfo").await?;
177 Ok(result)
178 }
179 pub async fn getutxos(&self, checkmempool: bool, txids: &[Txid]) -> Result<UtxoData, Error> {
181 let mut path = Vec::with_capacity(1 + if checkmempool { 0 } else { 1 } + txids.len());
182 path.push("getutxos".to_string());
183 if checkmempool {
184 path.push("checkmempool".to_string());
185 }
186 for (i, txid) in txids.iter().enumerate() {
187 path.push([txid.to_string(), i.to_string()].join("-"));
188 }
189 let result: UtxoData = self.call_json(&path.join("/")).await?;
190 Ok(result)
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use std::str::FromStr;
197 use super::*;
198 #[tokio::test]
199 async fn reqwest_fail() {
200 let rest = new("http://invalid-url");
201 assert!(rest.blockhashbyheight(0).await.is_err());
202 }
203 struct Fixture {
204 rest_env_name: &'static str,
205 genesis_block_hash: &'static str,
206 txid_coinbase_block1: &'static str,
207 }
208 async fn decode_fail(f: &Fixture) {
209 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
210 let rest = new(&test_endpoint);
211 assert!(rest.blockhashbyheight(0xFFFFFFFF).await.is_err());
212 }
213 async fn tx(f: &Fixture) {
214 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
215 let rest = new(&test_endpoint);
216 let tx = rest.tx(&Txid::from_str(f.txid_coinbase_block1).unwrap()).await.unwrap();
217 assert_eq!(tx.txid().to_string(), f.txid_coinbase_block1);
218 }
219 async fn block(f: &Fixture) {
220 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
221 let rest = new(&test_endpoint);
222 let blockid = BlockHash::from_str(f.genesis_block_hash).unwrap();
223 let block = rest.block(&blockid).await.unwrap();
224 assert_eq!(block.block_hash().to_string(), f.genesis_block_hash);
225 }
226 async fn block_notxdetails(f: &Fixture) {
227 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
228 let rest = new(&test_endpoint);
229 let blockid = BlockHash::from_str(f.genesis_block_hash).unwrap();
230 let blockheader = rest.block_notxdetails(&blockid).await.unwrap();
231 assert_eq!(blockheader.block_hash().to_string(), f.genesis_block_hash);
232 }
233 async fn headers(f: &Fixture) {
234 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
235 let rest = new(&test_endpoint);
236 let blockid = BlockHash::from_str(f.genesis_block_hash).unwrap();
237 let headers = rest.headers(1, &blockid).await.unwrap();
238 assert_eq!(headers[0].block_hash().to_string(), f.genesis_block_hash);
239 }
240 async fn chaininfo(f: &Fixture) {
241 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
242 let rest = new(&test_endpoint);
243 let chaininfo = rest.chaininfo().await.unwrap();
244 assert_eq!(chaininfo.chain, "main");
245 }
246 async fn blockhashbyheight(f: &Fixture) {
247 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
248 let rest = new(&test_endpoint);
249 assert_eq!(rest.blockhashbyheight(0).await.unwrap().to_string(), f.genesis_block_hash);
250 }
251 async fn blockhashbyheight_hex(f: &Fixture) {
252 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
253 let rest = new(&test_endpoint);
254 let blockhash_hex = rest.call_hex("blockhashbyheight/0").await.unwrap();
255 let blockhash = BlockHash::from_str(&blockhash_hex).unwrap();
256 assert_eq!(blockhash.to_string(), f.genesis_block_hash);
257 }
258 async fn utxos(f: &Fixture) {
259 let test_endpoint = std::env::var(f.rest_env_name).unwrap_or(DEFAULT_ENDPOINT.to_string());
260 let rest = new(&test_endpoint);
261 let utxos = rest.getutxos(true, &vec![
262 Txid::from_str(f.txid_coinbase_block1).unwrap(),
263 ]).await.unwrap();
264 assert!(utxos.chain_height > 0);
265 }
266 const BTC: Fixture = Fixture {
267 rest_env_name: "BITCOIN_REST_ENDPOINT",
268 genesis_block_hash: "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",
269 txid_coinbase_block1: "0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098",
270 };
271 #[tokio::test] async fn btc_decode_fail () { decode_fail (&BTC).await; }
272 #[tokio::test] async fn btc_tx () { tx (&BTC).await; }
273 #[tokio::test] async fn btc_block () { block (&BTC).await; }
274 #[tokio::test] async fn btc_block_notxdetails () { block_notxdetails (&BTC).await; }
275 #[tokio::test] async fn btc_headers () { headers (&BTC).await; }
276 #[tokio::test] async fn btc_chaininfo () { chaininfo (&BTC).await; }
277 #[tokio::test] async fn btc_blockhashbyheight () { blockhashbyheight (&BTC).await; }
278 #[tokio::test] async fn btc_blockhashbyheight_hex() { blockhashbyheight_hex(&BTC).await; }
279 #[tokio::test] async fn btc_utxos () { utxos (&BTC).await; }
280 const MONA: Fixture = Fixture {
281 rest_env_name: "MONACOIN_REST_ENDPOINT",
282 genesis_block_hash: "ff9f1c0116d19de7c9963845e129f9ed1bfc0b376eb54fd7afa42e0d418c8bb6",
283 txid_coinbase_block1: "10067abeabcd96a1261bc542b16d686d083308304923d74cb8f3bab4209cc3b9",
284 };
285 #[tokio::test] async fn mona_decode_fail () { decode_fail (&MONA).await; }
286 #[tokio::test] async fn mona_tx () { tx (&MONA).await; }
287 #[tokio::test] async fn mona_block () { block (&MONA).await; }
288 #[tokio::test] async fn mona_block_notxdetails() { block_notxdetails(&MONA).await; }
289 #[tokio::test] async fn mona_headers () { headers (&MONA).await; }
290 #[cfg(not(feature="softforks"))]
291 #[tokio::test] async fn mona_chaininfo () { chaininfo (&MONA).await; }
292 #[tokio::test] async fn mona_utxos () { utxos (&MONA).await; }
294}