bitcoin_rpc/
lib.rs

1// Copyright 2018 Jean Pierre Dudey <jeandudey@hotmail.com>
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6// option. This file may not be copied, modified, or distributed
7// except according to those terms.
8
9extern crate jsonrpc;
10
11extern crate serde;
12extern crate strason;
13
14extern crate bitcoin;
15extern crate bitcoin_rpc_json;
16
17extern crate failure;
18#[macro_use]
19extern crate failure_derive;
20
21/// Blockchain related RPC result types.
22pub mod blockchain {
23    pub use bitcoin_rpc_json::blockchain::*;
24}
25
26/// Mining related RPC result types.
27pub mod mining {
28    pub use bitcoin_rpc_json::mining::*;
29}
30
31/// Network related RPC result types.
32pub mod net {
33    pub use bitcoin_rpc_json::net::*;
34}
35
36use jsonrpc::client::Client;
37use strason::Json;
38
39use failure::ResultExt;
40
41macro_rules! rpc_request {
42    ($client:expr, $name:expr, $params:expr) => {
43        {
44            let request = $client.build_request($name, $params);
45            let response = $client.send_request(&request)
46                .context(ErrorKind::BadResponse)?;
47            response.into_result()
48                .context(ErrorKind::MalformedResponse)?
49        }
50    }
51}
52
53macro_rules! rpc_method {
54    (
55        $(#[$outer:meta])*
56        pub fn $rpc_method:ident(&self) -> RpcResult<$ty:ty>;
57    ) => {
58        $(#[$outer])*
59        pub fn $rpc_method(&self) -> $crate::RpcResult<$ty> {
60            let v: $ty = rpc_request!(&self.client,
61                                      stringify!($rpc_method).to_string(),
62                                      vec![]);
63            Ok(v)
64        }
65    };
66    (
67        $(#[$outer:meta])*
68        pub fn $rpc_method:ident(&self, $($param:ident : $pty:ty),+) -> RpcResult<$ty:ty>;
69    ) => {
70        $(#[$outer])*
71        pub fn $rpc_method(&self, $($param: $pty),+) -> $crate::RpcResult<$ty> {
72            let mut params = Vec::new();
73            $(
74                params.push(Json::from_serialize(&$param).unwrap());
75            )+
76
77            let v: $ty = rpc_request!(&self.client,
78                                      stringify!($rpc_method).to_string(),
79                                      params);
80            Ok(v)
81        }
82    }
83}
84
85pub type RpcResult<T> = Result<T, Error>;
86
87/// A Handle to a Bitcoin JSON-RPC connection
88pub struct BitcoinRpc {
89    client: Client,
90}
91
92impl BitcoinRpc {
93    /// Creates a client to a bitcoind JSON-RPC server.
94    pub fn new(url: String, user: Option<String>, pass: Option<String>) -> Self {
95        // Check that if we have a password, we have a username; other way
96        // around is ok.
97        debug_assert!(pass.is_none() || user.is_some());
98
99        BitcoinRpc { client: Client::new(url, user, pass) }
100    }
101
102    // blockchain
103
104    rpc_method! {
105        /// Returns the numbers of block in the longest chain.
106        pub fn getblockcount(&self) -> RpcResult<u64>;
107    }
108
109    rpc_method! {
110        // TODO: Use Sha256dHash from rust-bitcoin
111        /// Returns the hash of the best (tip) block in the longest blockchain.
112        pub fn getbestblockhash(&self) -> RpcResult<String>;
113    }
114
115    rpc_method! {
116        /// Waits for a specific new block and returns useful info about it.
117        /// Returns the current block on timeout or exit.
118        ///
119        /// # Arguments
120        ///
121        /// 1. `timeout`: Time in milliseconds to wait for a response. 0
122        /// indicates no timeout.
123        pub fn waitfornewblock(
124            &self,
125            timeout: u64
126        ) -> RpcResult<blockchain::BlockRef>;
127    }
128
129    rpc_method! {
130        /// Waits for a specific new block and returns useful info about it.
131        /// Returns the current block on timeout or exit.
132        ///
133        /// # Arguments
134        ///
135        /// 1. `blockhash`: Block hash to wait for.
136        /// 2. `timeout`: Time in milliseconds to wait for a response. 0
137        /// indicates no timeout.
138        // TODO: Use Sha256dHash
139        pub fn waitforblock(
140            &self,
141            blockhash: String,
142            timeout: u64
143        ) -> RpcResult<blockchain::BlockRef>;
144    }
145
146    rpc_method! {
147        /// Returns a data structure containing various state info regarding
148        /// blockchain processing.
149        pub fn getblockchaininfo(&self) -> RpcResult<blockchain::BlockchainInfo>;
150    }
151
152    // mining
153
154    pub fn estimatesmartfee<E>(
155        &self,
156        conf_target: u16,
157        estimate_mode: E,
158    ) -> Result<mining::EstimateSmartFee, Error>
159    where E:
160          Into<Option<mining::EstimateMode>>
161    {
162        let mut params = Vec::new();
163        params.push(Json::from_serialize(conf_target).unwrap());
164        if let Some(estimate_mode) = estimate_mode.into() {
165            params.push(Json::from_serialize(estimate_mode).unwrap())
166        }
167
168        let response = rpc_request!(&self.client,
169                                    "estimatesmartfee".to_string(),
170                                    params);
171        Ok(response)
172    }
173
174    // net
175    
176    rpc_method! {
177        /// Returns the number of connections to other nodes.
178        pub fn getconnectioncount(&self) -> RpcResult<u64>;
179    }
180
181    rpc_method! {
182        /// Requests that a ping be sent to all other nodes, to measure ping
183        /// time.
184        ///
185        /// Results provided in `getpeerinfo`, `pingtime` and `pingwait` fields
186        /// are decimal seconds.
187        ///
188        /// Ping command is handled in queue with all other commands, so it
189        /// measures processing backlog, not just network ping.
190        pub fn ping(&self) -> RpcResult<()>;
191    }
192
193    rpc_method! {
194        /// Returns data about each connected network node as an array of
195        /// [`PeerInfo`][]
196        ///
197        /// [`PeerInfo`]: net/struct.PeerInfo.html
198        pub fn getpeerinfo(&self) -> RpcResult<Vec<net::PeerInfo>>;
199    }
200
201    rpc_method! {
202        /// Attempts to add or remove a node from the addnode list.
203        ///
204        /// Or try a connection to a node once.
205        ///
206        /// Nodes added using `addnode` (or `-connect`) are protected from DoS
207        /// disconnection and are not required to be full nodes/support SegWit
208        /// as other outbound peers are (though such peers will not be synced
209        /// from).
210        ///
211        /// # Arguments:
212        ///
213        /// 1. `node`: The node (see [`getpeerinfo`][] for nodes)
214        /// 2. `command`: `AddNode::Add` to add a node to the list,
215        /// `AddNode::Remove` to remove a node from the list, `AddNode::OneTry`
216        /// to try a connection to the node once
217        ///
218        /// [`getpeerinfo`]: #method.getpeerinfo
219        pub fn addnode(
220            &self,
221            node: &str,
222            commnad: net::AddNode
223        ) -> RpcResult<()>;
224    }
225
226    rpc_method! {
227        pub fn getnetworkinfo(&self) -> RpcResult<net::NetworkInfo>;
228    }
229}
230
231/// The error type for bitcoin JSON-RPC operations.
232#[derive(Debug)]
233pub struct Error {
234    kind: failure::Context<ErrorKind>,
235}
236
237impl From<ErrorKind> for Error {
238    fn from(e: ErrorKind) -> Error {
239        Error {
240            kind: failure::Context::new(e),
241        }
242    }
243}
244
245impl From<failure::Context<ErrorKind>> for Error {
246    fn from(e: failure::Context<ErrorKind>) -> Error {
247        Error {
248            kind: e,
249        }
250    }
251}
252
253/// The kind of error.
254#[derive(Clone, Copy, Debug, Eq, PartialEq, Fail)]
255pub enum ErrorKind {
256    /// The request resulted in an error.
257    #[fail(display = "Request resulted in an error")]
258    BadResponse,
259    /// The received response format is malformed.
260    #[fail(display = "Response format is invalid")]
261    MalformedResponse,
262}