nanopyrs/rpc/
mod.rs

1mod encode;
2mod error;
3mod parse;
4
5pub mod debug;
6pub mod util;
7
8use crate::{Account, Block};
9use debug::DebugRpc;
10use json::{Map, Value as JsonValue};
11use serde_json as json;
12use zeroize::{Zeroize, ZeroizeOnDrop};
13
14#[cfg(feature = "serde")]
15use serde::{Deserialize, Serialize};
16
17pub use error::RpcError;
18
19#[cfg(test)]
20#[cfg(feature = "serde")]
21pub(crate) const USIZE_LEN: usize = std::mem::size_of::<usize>();
22
23/// General info about a block
24#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26pub struct BlockInfo {
27    /// Height of this block on the account's blockchain
28    pub height: usize,
29    /// Timestamp of when this block was created
30    pub timestamp: u64,
31    /// Whether or not this block has been confirmed
32    pub confirmed: bool,
33    /// The block
34    pub block: Block,
35}
36
37/// General info about an account
38#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40pub struct AccountInfo {
41    /// Hash of the frontier block of this account
42    pub frontier: [u8; 32],
43    /// Hash of the `open` block of this account
44    pub open_block: [u8; 32],
45    /// Balance of this account
46    pub balance: u128,
47    /// Timestamp of this account's last block
48    #[cfg_attr(feature = "serde", serde(rename = "timestamp"))]
49    pub modified_timestamp: u64,
50    /// Number of blocks in this account's history
51    pub block_count: usize,
52    /// The version of this account
53    pub version: usize,
54    /// The representative of this account
55    pub representative: Account,
56    /// The voting weight of this account
57    pub weight: u128,
58    /// The number of receivable transactions for this account
59    pub receivable: usize,
60}
61
62/// A receivable (pending) transaction.
63#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
64#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
65pub struct Receivable {
66    /// The recipient account of this transaction
67    pub recipient: Account,
68    /// The hash of the send block on the sender's account
69    #[cfg_attr(feature = "serde", serde(rename = "hash"))]
70    pub block_hash: [u8; 32],
71    /// The amount being transferred
72    pub amount: u128,
73}
74impl From<(Account, [u8; 32], u128)> for Receivable {
75    fn from(value: (Account, [u8; 32], u128)) -> Self {
76        Receivable {
77            recipient: value.0,
78            block_hash: value.1,
79            amount: value.2,
80        }
81    }
82}
83
84/// See the official [Nano RPC documentation](https://docs.nano.org/commands/rpc-protocol/) for details.
85#[derive(Debug, Clone)]
86pub struct Rpc(DebugRpc);
87impl Rpc {
88    pub fn new(url: &str, proxy: impl Into<Option<String>>) -> Result<Rpc, RpcError> {
89        Ok(Rpc(DebugRpc::new(url, proxy)?))
90    }
91
92    /// Get the URL of this RPC
93    pub fn get_url(&self) -> &str {
94        self.0.get_url()
95    }
96
97    /// Get the proxy of this RPC, if set
98    pub fn get_proxy(&self) -> Option<&str> {
99        self.0.get_proxy()
100    }
101
102    /// Same as `command`, but *everything* must be set manually
103    pub async fn _raw_request(&self, json: JsonValue) -> Result<JsonValue, RpcError> {
104        self.0._raw_request(json).await.result
105    }
106
107    /// Send a request to the node with `action` set to `[command]`, and setting the given `arguments`
108    pub async fn command(
109        &self,
110        command: &str,
111        arguments: Map<String, JsonValue>,
112    ) -> Result<JsonValue, RpcError> {
113        self.0.command(command, arguments).await.result
114    }
115
116    pub async fn account_balance(&self, account: &Account) -> Result<u128, RpcError> {
117        self.0.account_balance(account).await.result
118    }
119
120    /// Lists the account's blocks, starting at `head` (or the newest block if `head` is `None`), and going back at most `count` number of blocks.
121    /// Will stop at first legacy block.
122    pub async fn account_history(
123        &self,
124        account: &Account,
125        count: usize,
126        head: Option<[u8; 32]>,
127        offset: Option<usize>,
128    ) -> Result<Vec<Block>, RpcError> {
129        self.0
130            .account_history(account, count, head, offset)
131            .await
132            .result
133    }
134
135    /// Gets general information about an account.
136    /// Returns `None` if the account has not been opened.
137    pub async fn account_info(&self, account: &Account) -> Result<Option<AccountInfo>, RpcError> {
138        self.0.account_info(account).await.result
139    }
140
141    /// Indirect, relies on `account_history`.
142    /// This allows the data to be verified to an extent.
143    ///
144    /// If an account is not yet opened, its representative will be returned as `None`.
145    pub async fn account_representative(
146        &self,
147        account: &Account,
148    ) -> Result<Option<Account>, RpcError> {
149        self.0.account_representative(account).await.result
150    }
151
152    pub async fn accounts_balances(&self, accounts: &[Account]) -> Result<Vec<u128>, RpcError> {
153        self.0.accounts_balances(accounts).await.result
154    }
155
156    /// Returns the hash of the frontier (newest) block of the given accounts.
157    /// If an account is not yet opened, its frontier will be returned as `None`.
158    pub async fn accounts_frontiers(
159        &self,
160        accounts: &[Account],
161    ) -> Result<Vec<Option<[u8; 32]>>, RpcError> {
162        self.0.accounts_frontiers(accounts).await.result
163    }
164
165    /// For each account, returns the receivable transactions as `Vec<Receivable>`
166    pub async fn accounts_receivable(
167        &self,
168        accounts: &[Account],
169        count: usize,
170        threshold: u128,
171    ) -> Result<Vec<Vec<Receivable>>, RpcError> {
172        self.0
173            .accounts_receivable(accounts, count, threshold)
174            .await
175            .result
176    }
177
178    /// If an account is not yet opened, its representative will be returned as `None`
179    pub async fn accounts_representatives(
180        &self,
181        accounts: &[Account],
182    ) -> Result<Vec<Option<Account>>, RpcError> {
183        self.0.accounts_representatives(accounts).await.result
184    }
185
186    /// Legacy blocks, and blocks that don't exist, will return `None`
187    pub async fn block_info(&self, hash: [u8; 32]) -> Result<Option<BlockInfo>, RpcError> {
188        self.0.block_info(hash).await.result
189    }
190
191    /// Legacy blocks, and blocks that don't exist, will return `None`
192    pub async fn blocks_info(
193        &self,
194        hashes: &[[u8; 32]],
195    ) -> Result<Vec<Option<BlockInfo>>, RpcError> {
196        self.0.blocks_info(hashes).await.result
197    }
198
199    /// Returns the hash of the block
200    pub async fn process(&self, block: &Block) -> Result<[u8; 32], RpcError> {
201        self.0.process(block).await.result
202    }
203
204    /// Returns the generated work, assuming no error is encountered
205    pub async fn work_generate(
206        &self,
207        work_hash: [u8; 32],
208        custom_difficulty: Option<[u8; 8]>,
209    ) -> Result<[u8; 8], RpcError> {
210        self.0
211            .work_generate(work_hash, custom_difficulty)
212            .await
213            .result
214    }
215}
216
217#[cfg(test)]
218#[cfg(feature = "serde")]
219mod serde_tests {
220    use super::*;
221    use crate::{
222        constants::get_genesis_account, constants::ONE_NANO, serde_test, Block, BlockType,
223        Signature,
224    };
225
226    serde_test!(block_info: BlockInfo {
227        height: 939,
228        timestamp: 3902193,
229        confirmed: true,
230        block: Block {
231            block_type: BlockType::Receive,
232            account: get_genesis_account(),
233            previous: [19; 32],
234            representative: get_genesis_account(),
235            balance: ONE_NANO,
236            link: [91; 32],
237            signature: Signature::default(),
238            work: [22; 8]
239        }
240    } => USIZE_LEN + 8 + 1 + 220);
241
242    serde_test!(account_info: AccountInfo {
243        frontier: [92; 32],
244        open_block: [192; 32],
245        balance: 89823892,
246        modified_timestamp: 8932,
247        block_count: 483928329,
248        version: 2,
249        representative: get_genesis_account(),
250        weight: 8439483,
251        receivable: 100
252    } => 32 + 32 + 16 + 8 + USIZE_LEN + USIZE_LEN + 32 + 16 + USIZE_LEN);
253
254    serde_test!(receivable: Receivable {
255        recipient: get_genesis_account(),
256        block_hash: [51; 32],
257        amount: 432894284243
258    } => 32 + 32 + 16);
259}