• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Neptune-Crypto / neptune-core / 13812800970

12 Mar 2025 01:39PM UTC coverage: 84.198% (+0.2%) from 84.013%
13812800970

push

github

Sword-Smith
feat(rpc_server): Get all public announcements in block

Add an RPC server endpoint to get all public announcements contained in
a specified block.

92 of 93 new or added lines in 1 file covered. (98.92%)

6 existing lines in 4 files now uncovered.

50613 of 60112 relevant lines covered (84.2%)

177125.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.06
/src/rpc_server.rs
1
//! implements an RPC server and client based on [tarpc]
2
//!
3
//! request and response data is json serialized.
4
//!
5
//! It is presently easiest to create a tarpc client in rust.
6
//! To do so, one should add neptune-cash as a dependency and
7
//! then do something like:
8
//!
9
//! ```no_run
10
//! use anyhow::Result;
11
//! use neptune_cash::rpc_server::RPCClient;
12
//! use neptune_cash::rpc_auth;
13
//! use tarpc::tokio_serde::formats::Json;
14
//! use tarpc::serde_transport::tcp;
15
//! use tarpc::client;
16
//! use tarpc::context;
17
//!
18
//! # #[tokio::main]
19
//! # async fn main() -> Result<()>{
20
//! // create a serde/json transport over tcp.
21
//! let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
22
//!
23
//! // create an rpc client using the transport.
24
//! let client = RPCClient::new(client::Config::default(), transport).spawn();
25
//!
26
//! // query neptune-core server how to find the cookie file
27
//! let cookie_hint = client.cookie_hint(context::current()).await.unwrap().unwrap();
28
//!
29
//! // load the cookie file from disk and assign it to a token.
30
//! let token: rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
31
//!
32
//! // query any RPC API, passing the auth token.  here we query block_height.
33
//! let block_height = client.block_height(context::current(), token).await??;
34
//! # Ok(())
35
//! # }
36
//! ```
37
//!
38
//! For other languages, one would need to connect to the RPC TCP port and then
39
//! manually construct the appropriate json method call.  Examples of this will
40
//! be forthcoming in the future.
41
//!
42
//! See [rpc_auth] for descriptions of the authentication mechanisms.
43
//!
44
//! Every RPC method returns an [RpcResult] which is wrapped inside a
45
//! [tarpc::Response] by the rpc server.
46
use std::collections::HashMap;
47
use std::net::IpAddr;
48
use std::net::SocketAddr;
49

50
use anyhow::anyhow;
51
use anyhow::Result;
52
use get_size2::GetSize;
53
use itertools::Itertools;
54
use num_traits::Zero;
55
use serde::Deserialize;
56
use serde::Serialize;
57
use systemstat::Platform;
58
use systemstat::System;
59
use tarpc::context;
60
use tasm_lib::twenty_first::prelude::Mmr;
61
use tracing::error;
62
use tracing::info;
63
use tracing::warn;
64
use twenty_first::math::digest::Digest;
65

66
use crate::config_models::network::Network;
67
use crate::macros::fn_name;
68
use crate::macros::log_slow_scope;
69
use crate::mine_loop::precalculate_block_auth_paths;
70
use crate::models::blockchain::block::block_header::BlockHeader;
71
use crate::models::blockchain::block::block_height::BlockHeight;
72
use crate::models::blockchain::block::block_info::BlockInfo;
73
use crate::models::blockchain::block::block_kernel::BlockKernel;
74
use crate::models::blockchain::block::block_selector::BlockSelector;
75
use crate::models::blockchain::block::difficulty_control::Difficulty;
76
use crate::models::blockchain::block::Block;
77
use crate::models::blockchain::transaction::PublicAnnouncement;
78
use crate::models::blockchain::transaction::Transaction;
79
use crate::models::blockchain::transaction::TransactionProof;
80
use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
81
use crate::models::channel::ClaimUtxoData;
82
use crate::models::channel::RPCServerToMain;
83
use crate::models::peer::peer_info::PeerInfo;
84
use crate::models::peer::InstanceId;
85
use crate::models::peer::PeerStanding;
86
use crate::models::proof_abstractions::mast_hash::MastHash;
87
use crate::models::proof_abstractions::timestamp::Timestamp;
88
use crate::models::state::mining_state::MAX_NUM_EXPORTED_BLOCK_PROPOSAL_STORED;
89
use crate::models::state::mining_status::MiningStatus;
90
use crate::models::state::transaction_kernel_id::TransactionKernelId;
91
use crate::models::state::tx_proving_capability::TxProvingCapability;
92
use crate::models::state::wallet::address::encrypted_utxo_notification::EncryptedUtxoNotification;
93
use crate::models::state::wallet::address::KeyType;
94
use crate::models::state::wallet::address::ReceivingAddress;
95
use crate::models::state::wallet::address::SpendingKey;
96
use crate::models::state::wallet::coin_with_possible_timelock::CoinWithPossibleTimeLock;
97
use crate::models::state::wallet::expected_utxo::UtxoNotifier;
98
use crate::models::state::wallet::incoming_utxo::IncomingUtxo;
99
use crate::models::state::wallet::monitored_utxo::MonitoredUtxo;
100
use crate::models::state::wallet::utxo_notification::PrivateNotificationData;
101
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;
102
use crate::models::state::wallet::wallet_status::WalletStatus;
103
use crate::models::state::GlobalState;
104
use crate::models::state::GlobalStateLock;
105
use crate::prelude::twenty_first;
106
use crate::rpc_auth;
107
use crate::twenty_first::prelude::Tip5;
108
use crate::DataDirectory;
109

110
/// result returned by RPC methods
111
pub type RpcResult<T> = Result<T, error::RpcError>;
112

113
#[derive(Clone, Debug, Serialize, Deserialize)]
114
pub struct DashBoardOverviewDataFromClient {
115
    pub tip_digest: Digest,
116
    pub tip_header: BlockHeader,
117
    pub syncing: bool,
118
    pub confirmed_available_balance: NativeCurrencyAmount,
119
    pub confirmed_total_balance: NativeCurrencyAmount,
120
    pub unconfirmed_available_balance: NativeCurrencyAmount,
121
    pub unconfirmed_total_balance: NativeCurrencyAmount,
122
    pub mempool_size: usize,
123
    pub mempool_total_tx_count: usize,
124
    pub mempool_own_tx_count: usize,
125

126
    // `None` symbolizes failure in getting peer count
127
    pub peer_count: Option<usize>,
128
    pub max_num_peers: usize,
129

130
    // `None` symbolizes failure to get mining status
131
    pub mining_status: Option<MiningStatus>,
132

133
    pub proving_capability: TxProvingCapability,
134

135
    // # of confirmations of the last wallet balance change.
136
    //
137
    // Starts at 1, as the block in which a tx is included is considered the 1st
138
    // confirmation.
139
    //
140
    // `None` indicates that wallet balance has never changed.
141
    pub confirmations: Option<BlockHeight>,
142

143
    /// CPU temperature in degrees Celsius
144
    pub cpu_temp: Option<f32>,
145
}
146

147
#[derive(Clone, Debug, Copy, Serialize, Deserialize, strum::Display)]
148
pub enum TransactionProofType {
149
    SingleProof,
150
    ProofCollection,
151
    PrimitiveWitness,
152
}
153

154
#[derive(Clone, Debug, Copy, Serialize, Deserialize)]
155
pub struct MempoolTransactionInfo {
156
    pub id: TransactionKernelId,
157
    pub proof_type: TransactionProofType,
158
    pub num_inputs: usize,
159
    pub num_outputs: usize,
160
    pub positive_balance_effect: NativeCurrencyAmount,
161
    pub negative_balance_effect: NativeCurrencyAmount,
162
    pub fee: NativeCurrencyAmount,
163
    pub synced: bool,
164
}
165

166
impl From<&Transaction> for MempoolTransactionInfo {
167
    fn from(mptx: &Transaction) -> Self {
×
168
        MempoolTransactionInfo {
×
169
            id: mptx.kernel.txid(),
×
170
            proof_type: match mptx.proof {
×
171
                TransactionProof::Witness(_) => TransactionProofType::PrimitiveWitness,
×
172
                TransactionProof::SingleProof(_) => TransactionProofType::SingleProof,
×
173
                TransactionProof::ProofCollection(_) => TransactionProofType::ProofCollection,
×
174
            },
175
            num_inputs: mptx.kernel.inputs.len(),
×
176
            num_outputs: mptx.kernel.outputs.len(),
×
177
            positive_balance_effect: NativeCurrencyAmount::zero(),
×
178
            negative_balance_effect: NativeCurrencyAmount::zero(),
×
179
            fee: mptx.kernel.fee,
×
180
            synced: false,
×
181
        }
×
182
    }
×
183
}
184

185
impl MempoolTransactionInfo {
186
    pub(crate) fn with_positive_effect_on_balance(
×
187
        mut self,
×
188
        positive_balance_effect: NativeCurrencyAmount,
×
189
    ) -> Self {
×
190
        self.positive_balance_effect = positive_balance_effect;
×
191
        self
×
192
    }
×
193

194
    pub(crate) fn with_negative_effect_on_balance(
×
195
        mut self,
×
196
        negative_balance_effect: NativeCurrencyAmount,
×
197
    ) -> Self {
×
198
        self.negative_balance_effect = negative_balance_effect;
×
199
        self
×
200
    }
×
201

202
    pub fn synced(mut self) -> Self {
×
203
        self.synced = true;
×
204
        self
×
205
    }
×
206
}
207

208
/// Data required to attempt to solve the proof-of-work puzzle that allows the
209
/// minting of the next block.
210
#[derive(Clone, Debug, Copy, Serialize, Deserialize)]
211
pub struct ProofOfWorkPuzzle {
212
    kernel_auth_path: [Digest; BlockKernel::MAST_HEIGHT],
213
    header_auth_path: [Digest; BlockHeader::MAST_HEIGHT],
214

215
    /// The threshold digest that defines when a PoW solution is valid. The
216
    /// block's hash must be less than or equal to this value.
217
    threshold: Digest,
218

219
    /// The total reward, timelocked plus liquid, for a successful guess
220
    total_guesser_reward: NativeCurrencyAmount,
221

222
    /// An identifier for the puzzle. Needed since more than one block proposal
223
    /// may be known for the next block. A commitment to the entire block
224
    /// kernel, apart from the nonce.
225
    id: Digest,
226
}
227

228
impl ProofOfWorkPuzzle {
229
    /// Return a PoW puzzle assuming that the caller has already set the correct
230
    /// guesser digest.
231
    fn new(block_proposal: Block, latest_block_header: BlockHeader) -> Self {
15✔
232
        let guesser_reward = block_proposal.total_guesser_reward();
15✔
233
        let (kernel_auth_path, header_auth_path) = precalculate_block_auth_paths(&block_proposal);
15✔
234
        let threshold = latest_block_header.difficulty.target();
15✔
235

15✔
236
        let id = Tip5::hash(&(kernel_auth_path, header_auth_path));
15✔
237

15✔
238
        Self {
15✔
239
            kernel_auth_path,
15✔
240
            header_auth_path,
15✔
241
            threshold,
15✔
242
            total_guesser_reward: guesser_reward,
15✔
243
            id,
15✔
244
        }
15✔
245
    }
15✔
246
}
247

248
#[tarpc::service]
×
249
pub trait RPC {
250
    /******** READ DATA ********/
251
    // Place all methods that only read here
252
    /// Returns a [rpc_auth::CookieHint] for purposes of zero-conf authentication
253
    ///
254
    /// The CookieHint provides a location for the cookie file used by this
255
    /// neptune-core instance as well as the [Network].
256
    ///
257
    /// ```no_run
258
    /// # use anyhow::Result;
259
    /// # use neptune_cash::rpc_server::RPCClient;
260
    /// # use neptune_cash::rpc_auth;
261
    /// # use tarpc::tokio_serde::formats::Json;
262
    /// # use tarpc::serde_transport::tcp;
263
    /// # use tarpc::client;
264
    /// # use tarpc::context;
265
    /// #
266
    /// # #[tokio::main]
267
    /// # async fn main() -> Result<()>{
268
    /// # // create a serde/json transport over tcp.
269
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
270
    /// #
271
    /// # // create an rpc client using the transport.
272
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
273
    /// #
274
    /// // query neptune-core server how to find the cookie file
275
    /// let cookie_hint = client.cookie_hint(context::current()).await??;
276
    /// # Ok(())
277
    /// # }
278
    /// ```
279
    /// this method does not require authentication because local clients must
280
    /// be able to call this method in order to bootstrap cookie-based
281
    /// authentication.
282
    ///
283
    async fn cookie_hint() -> RpcResult<rpc_auth::CookieHint>;
284

285
    /// Return the network this neptune-core instance is running
286
    ///
287
    /// ```no_run
288
    /// # use anyhow::Result;
289
    /// # use neptune_cash::rpc_server::RPCClient;
290
    /// # use neptune_cash::rpc_auth;
291
    /// # use tarpc::tokio_serde::formats::Json;
292
    /// # use tarpc::serde_transport::tcp;
293
    /// # use tarpc::client;
294
    /// # use tarpc::context;
295
    /// #
296
    /// #
297
    /// # #[tokio::main]
298
    /// # async fn main() -> Result<()>{
299
    /// # // create a serde/json transport over tcp.
300
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
301
    /// #
302
    /// # // create an rpc client using the transport.
303
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
304
    /// #
305
    /// // query neptune-core server the network it is running on.
306
    /// let network = client.network(context::current()).await??;
307
    /// # Ok(())
308
    /// # }
309
    /// ```
310
    async fn network() -> RpcResult<Network>;
311

312
    /// Returns local socket used for incoming peer-connections. Does not show
313
    /// the public IP address, as the client does not know this.
314
    ///
315
    /// ```no_run
316
    /// # use anyhow::Result;
317
    /// # use neptune_cash::rpc_server::RPCClient;
318
    /// # use neptune_cash::rpc_auth;
319
    /// # use tarpc::tokio_serde::formats::Json;
320
    /// # use tarpc::serde_transport::tcp;
321
    /// # use tarpc::client;
322
    /// # use tarpc::context;
323
    /// #
324
    /// # #[tokio::main]
325
    /// # async fn main() -> Result<()>{
326
    /// #
327
    /// # // create a serde/json transport over tcp.
328
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
329
    /// #
330
    /// # // create an rpc client using the transport.
331
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
332
    /// #
333
    /// # // Defines cookie hint
334
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
335
    /// #
336
    /// # // load the cookie file from disk and assign it to a token
337
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
338
    /// #
339
    /// // query neptune-core server to get local socket used for incoming peer-connections.
340
    /// let own_listen_address = client.own_listen_address_for_peers(context::current(), token).await??;
341
    /// # Ok(())
342
    /// # }
343
    /// ```
344
    async fn own_listen_address_for_peers(token: rpc_auth::Token) -> RpcResult<Option<SocketAddr>>;
345

346
    /// Return the node's instance-ID which is a globally unique random generated number
347
    /// set at startup used to ensure that the node does not connect to itself, or the
348
    /// same peer twice.
349
    ///
350
    /// ```no_run
351
    /// # use anyhow::Result;
352
    /// # use neptune_cash::rpc_server::RPCClient;
353
    /// # use neptune_cash::rpc_auth;
354
    /// # use tarpc::tokio_serde::formats::Json;
355
    /// # use tarpc::serde_transport::tcp;
356
    /// # use tarpc::client;
357
    /// # use tarpc::context;
358
    /// #
359
    /// # #[tokio::main]
360
    /// # async fn main() -> Result<()>{
361
    /// #
362
    /// # // create a serde/json transport over tcp.
363
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
364
    /// #
365
    /// # // create an rpc client using the transport.
366
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
367
    /// #
368
    /// # // Defines cookie hint
369
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
370
    /// #
371
    /// # // load the cookie file from disk and assign it to a token
372
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
373
    /// #
374
    /// // query neptune-core server to get own instance ID.
375
    /// let own_instance_id = client.own_instance_id(context::current(), token).await??;
376
    /// # Ok(())
377
    /// # }
378
    /// ```
379
    async fn own_instance_id(token: rpc_auth::Token) -> RpcResult<InstanceId>;
380

381
    /// Returns the current block height.
382
    ///
383
    /// ```no_run
384
    /// # use anyhow::Result;
385
    /// # use neptune_cash::rpc_server::RPCClient;
386
    /// # use neptune_cash::rpc_auth;
387
    /// # use tarpc::tokio_serde::formats::Json;
388
    /// # use tarpc::serde_transport::tcp;
389
    /// # use tarpc::client;
390
    /// # use tarpc::context;
391
    /// #
392
    /// # #[tokio::main]
393
    /// # async fn main() -> Result<()>{
394
    /// #
395
    /// # // create a serde/json transport over tcp.
396
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
397
    /// #
398
    /// # // create an rpc client using the transport.
399
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
400
    /// #
401
    /// # // Defines cookie hint
402
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
403
    /// #
404
    /// # // load the cookie file from disk and assign it to a token
405
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
406
    /// #
407
    /// // query neptune-core server to get the block height.
408
    /// let block_height = client.block_height(context::current(), token).await??;
409
    /// # Ok(())
410
    /// # }
411
    /// ```
412
    async fn block_height(token: rpc_auth::Token) -> RpcResult<BlockHeight>;
413

414
    /// Returns the number of blocks (confirmations) since wallet balance last changed.
415
    ///
416
    /// returns `Option<BlockHeight>`
417
    ///
418
    /// return value will be None if wallet has not received any incoming funds.
419
    ///
420
    /// ```no_run
421
    /// # use anyhow::Result;
422
    /// # use neptune_cash::rpc_server::RPCClient;
423
    /// # use neptune_cash::rpc_auth;
424
    /// # use tarpc::tokio_serde::formats::Json;
425
    /// # use tarpc::serde_transport::tcp;
426
    /// # use tarpc::client;
427
    /// # use tarpc::context;
428
    /// #
429
    /// # #[tokio::main]
430
    /// # async fn main() -> Result<()>{
431
    /// #
432
    /// # // create a serde/json transport over tcp.
433
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
434
    /// #
435
    /// # // create an rpc client using the transport.
436
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
437
    /// #
438
    /// # // Defines cookie hint
439
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
440
    /// #
441
    /// # // load the cookie file from disk and assign it to a token
442
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
443
    /// #
444
    /// // query neptune-core server to get the blocks since wallet balance changed.
445
    /// let block_height = client.confirmations(context::current(), token).await??;
446
    /// # Ok(())
447
    /// # }
448
    /// ```
449
    async fn confirmations(token: rpc_auth::Token) -> RpcResult<Option<BlockHeight>>;
450

451
    /// Returns info about the peers we are connected to
452
    ///
453
    /// return value will be None if wallet has not received any incoming funds.
454
    ///
455
    /// ```no_run
456
    /// # use anyhow::Result;
457
    /// # use neptune_cash::rpc_server::RPCClient;
458
    /// # use neptune_cash::rpc_auth;
459
    /// # use tarpc::tokio_serde::formats::Json;
460
    /// # use tarpc::serde_transport::tcp;
461
    /// # use tarpc::client;
462
    /// # use tarpc::context;
463
    /// #
464
    /// # #[tokio::main]
465
    /// # async fn main() -> anyhow::Result<()>{
466
    /// #
467
    /// # // create a serde/json transport over tcp.
468
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
469
    /// #
470
    /// # // create an rpc client using the transport.
471
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
472
    /// #
473
    /// # // Defines cookie hint
474
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
475
    /// #
476
    /// # // load the cookie file from disk and assign it to a token
477
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
478
    /// #
479
    /// // query neptune-core server to get the info about the peers we are connected to
480
    /// let peers = client.peer_info(context::current(), token).await??;
481
    /// # Ok(())
482
    /// # }
483
    /// ```
484
    async fn peer_info(token: rpc_auth::Token) -> RpcResult<Vec<PeerInfo>>;
485

486
    /// Return info about all peers that have been negatively sanctioned.
487
    ///
488
    /// return value will be None if wallet has not received any incoming funds.
489
    ///
490
    /// ```no_run
491
    /// # use anyhow::Result;
492
    /// # use neptune_cash::rpc_server::RPCClient;
493
    /// # use neptune_cash::rpc_auth;
494
    /// # use tarpc::tokio_serde::formats::Json;
495
    /// # use tarpc::serde_transport::tcp;
496
    /// # use tarpc::client;
497
    /// # use tarpc::context;
498
    /// #
499
    /// # #[tokio::main]
500
    /// # async fn main() -> Result<()>{
501
    /// #
502
    /// # // create a serde/json transport over tcp.
503
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
504
    /// #
505
    /// # // create an rpc client using the transport.
506
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
507
    /// #
508
    /// # // Defines cookie hint
509
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
510
    /// #
511
    /// # // load the cookie file from disk and assign it to a token
512
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
513
    /// #
514
    /// // query neptune-core server to get the info about the peers that are negatively sanctioned
515
    /// let punished_peers = client.all_punished_peers(context::current(), token).await??;
516
    /// # Ok(())
517
    /// # }
518
    /// ```
519
    async fn all_punished_peers(token: rpc_auth::Token)
520
        -> RpcResult<HashMap<IpAddr, PeerStanding>>;
521

522
    /// Returns the digest of the latest n blocks
523
    ///
524
    /// ```no_run
525
    /// # use anyhow::Result;
526
    /// # use neptune_cash::rpc_server::RPCClient;
527
    /// # use neptune_cash::rpc_auth;
528
    /// # use tarpc::tokio_serde::formats::Json;
529
    /// # use tarpc::serde_transport::tcp;
530
    /// # use tarpc::client;
531
    /// # use tarpc::context;
532
    /// #
533
    /// # #[tokio::main]
534
    /// # async fn main() -> Result<()>{
535
    /// #
536
    /// # // create a serde/json transport over tcp.
537
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
538
    /// #
539
    /// # // create an rpc client using the transport.
540
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
541
    /// #
542
    /// # // Defines cookie hint
543
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
544
    /// #
545
    /// # // load the cookie file from disk and assign it to a token
546
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
547
    /// #
548
    /// // number of latest blocks digests you want to get
549
    /// let n : usize = 10;
550
    ///
551
    /// // query neptune-core server to get the digests of the n latest blocks
552
    /// let latest_tip_digests = client.latest_tip_digests(context::current(), token, n).await??;
553
    /// # Ok(())
554
    /// # }
555
    /// ```
556
    async fn latest_tip_digests(token: rpc_auth::Token, n: usize) -> RpcResult<Vec<Digest>>;
557

558
    /// Returns information about the specified block if found
559
    ///
560
    /// ```no_run
561
    /// # use anyhow::Result;
562
    /// use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
563
    /// # use neptune_cash::rpc_server::RPCClient;
564
    /// # use neptune_cash::rpc_auth;
565
    /// # use tarpc::tokio_serde::formats::Json;
566
    /// # use tarpc::serde_transport::tcp;
567
    /// # use tarpc::client;
568
    /// # use tarpc::context;
569
    /// #
570
    /// # #[tokio::main]
571
    /// # async fn main() -> Result<()>{
572
    /// #
573
    /// # // create a serde/json transport over tcp.
574
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
575
    /// #
576
    /// # // create an rpc client using the transport.
577
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
578
    /// #
579
    /// # // Defines cookie hint
580
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
581
    /// #
582
    /// # // load the cookie file from disk and assign it to a token
583
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
584
    /// #
585
    /// // set the way to look up for a block : it can be `Digest`, `Height`, `Genesis`, `Tip`
586
    /// let block_selector : BlockSelector = BlockSelector::Genesis;
587
    ///
588
    /// // query neptune-core server to get block info
589
    /// let latest_tip_digests = client.block_info(context::current(), token, block_selector).await??;
590
    /// # Ok(())
591
    /// # }
592
    /// ```
593
    async fn block_info(
594
        token: rpc_auth::Token,
595
        block_selector: BlockSelector,
596
    ) -> RpcResult<Option<BlockInfo>>;
597

598
    /// Return the public announements contained in a specified block.
599
    ///
600
    /// Returns `None` if the selected block could not be found, otherwise
601
    /// returns `Some(public_announcements)`.
602
    ///
603
    /// Does not attempt to decode the public announcements.
604
    async fn public_announcements_in_block(
605
        token: rpc_auth::Token,
606
        block_selector: BlockSelector,
607
    ) -> RpcResult<Option<Vec<PublicAnnouncement>>>;
608

609
    /// Return the digests of known blocks with specified height.
610
    ///
611
    /// ```no_run
612
    /// # use anyhow::Result;
613
    /// use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
614
    /// use neptune_cash::models::blockchain::block::block_height::BlockHeight;
615
    /// # use neptune_cash::rpc_server::RPCClient;
616
    /// # use neptune_cash::rpc_auth;
617
    /// # use tarpc::tokio_serde::formats::Json;
618
    /// # use tarpc::serde_transport::tcp;
619
    /// # use tarpc::client;
620
    /// # use tarpc::context;
621
    /// #
622
    /// # #[tokio::main]
623
    /// # async fn main() -> Result<()>{
624
    /// #
625
    /// # // create a serde/json transport over tcp.
626
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
627
    /// #
628
    /// # // create an rpc client using the transport.
629
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
630
    /// #
631
    /// # // Defines cookie hint
632
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
633
    /// #
634
    /// # // load the cookie file from disk and assign it to a token
635
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
636
    /// #
637
    /// // set block height value to genesis bloc
638
    /// let height : BlockHeight = BlockHeight::genesis();
639
    ///
640
    /// // query neptune-core server to block digests by height
641
    /// let block_digests_by_height = client.block_digests_by_height(context::current(), token, height).await??;
642
    /// # Ok(())
643
    /// # }
644
    /// ```
645
    async fn block_digests_by_height(
646
        token: rpc_auth::Token,
647
        height: BlockHeight,
648
    ) -> RpcResult<Vec<Digest>>;
649

650
    /// Return the digest for the specified block if found
651
    ///
652
    /// ```no_run
653
    /// # use anyhow::Result;
654
    /// use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
655
    /// # use neptune_cash::rpc_server::RPCClient;
656
    /// # use neptune_cash::rpc_auth;
657
    /// # use tarpc::tokio_serde::formats::Json;
658
    /// # use tarpc::serde_transport::tcp;
659
    /// # use tarpc::client;
660
    /// # use tarpc::context;
661
    /// #
662
    /// # #[tokio::main]
663
    /// # async fn main() -> Result<()>{
664
    /// #
665
    /// # // create a serde/json transport over tcp.
666
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
667
    /// #
668
    /// # // create an rpc client using the transport.
669
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
670
    /// #
671
    /// # // Defines cookie hint
672
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
673
    /// #
674
    /// # // load the cookie file from disk and assign it to a token
675
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
676
    /// #
677
    /// // set the way to look up for a block : it can be `Digest`, `Height`, `Genesis`, `Tip`
678
    /// let block_selector : BlockSelector = BlockSelector::Tip;
679
    ///
680
    /// // query neptune-core server to get block digest
681
    /// let block_digest = client.block_digest(context::current(), token, block_selector).await??;
682
    /// # Ok(())
683
    /// # }
684
    /// ```
685
    async fn block_digest(
686
        token: rpc_auth::Token,
687
        block_selector: BlockSelector,
688
    ) -> RpcResult<Option<Digest>>;
689

690
    /// Return the digest for the specified UTXO leaf index if found
691
    ///
692
    /// ```no_run
693
    /// # use anyhow::Result;
694
    /// use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
695
    /// # use neptune_cash::rpc_server::RPCClient;
696
    /// # use neptune_cash::rpc_auth;
697
    /// # use tarpc::tokio_serde::formats::Json;
698
    /// # use tarpc::serde_transport::tcp;
699
    /// # use tarpc::client;
700
    /// # use tarpc::context;
701
    /// #
702
    /// # #[tokio::main]
703
    /// # async fn main() -> Result<()>{
704
    /// #
705
    /// // create a serde/json transport over tcp.
706
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
707
    /// #
708
    /// # // create an rpc client using the transport.
709
    /// let client = RPCClient::new(client::Config::default(), transport).spawn();
710
    /// #
711
    /// # // Defines cookie hint
712
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
713
    /// #
714
    /// # // load the cookie file from disk and assign it to a token
715
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
716
    /// #
717
    /// // leaf index is set to 5
718
    /// let leaf_index : u64 = 5;
719
    ///
720
    /// // query neptune-core server to get utxo digest
721
    /// let block_digest = client.utxo_digest(context::current(), token, leaf_index).await??;
722
    /// # Ok(())
723
    /// # }
724
    /// ```
725
    async fn utxo_digest(token: rpc_auth::Token, leaf_index: u64) -> RpcResult<Option<Digest>>;
726

727
    /// Return the block header for the specified block
728
    ///
729
    /// ```no_run
730
    /// # use anyhow::Result;
731
    /// use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
732
    /// # use neptune_cash::rpc_server::RPCClient;
733
    /// # use neptune_cash::rpc_auth;
734
    /// # use tarpc::tokio_serde::formats::Json;
735
    /// # use tarpc::serde_transport::tcp;
736
    /// # use tarpc::client;
737
    /// # use tarpc::context;
738
    /// #
739
    /// # #[tokio::main]
740
    /// # async fn main() -> Result<()>{
741
    /// #
742
    /// # // create a serde/json transport over tcp.
743
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
744
    /// #
745
    /// # // create an rpc client using the transport.
746
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
747
    /// #
748
    /// # // Defines cookie hint
749
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
750
    /// #
751
    /// # // load the cookie file from disk and assign it to a token
752
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
753
    /// #
754
    /// // set the way to look up for a block : it can be `Digest`, `Height`, `Genesis`, `Tip`
755
    /// let block_selector : BlockSelector = BlockSelector::Genesis;
756
    ///
757
    /// // query neptune-core server to get block header
758
    /// let block_header = client.header(context::current(), token, block_selector).await??;
759
    /// # Ok(())
760
    /// # }
761
    /// ```
762
    async fn header(
763
        token: rpc_auth::Token,
764
        block_selector: BlockSelector,
765
    ) -> RpcResult<Option<BlockHeader>>;
766

767
    /// Get sum of confirmed, unspent, available UTXOs
768
    /// excludes time-locked utxos
769
    /// ```no_run
770
    /// # use anyhow::Result;
771
    /// # use neptune_cash::rpc_server::RPCClient;
772
    /// # use neptune_cash::rpc_auth;
773
    /// # use tarpc::tokio_serde::formats::Json;
774
    /// # use tarpc::serde_transport::tcp;
775
    /// # use tarpc::client;
776
    /// # use tarpc::context;
777
    /// #
778
    /// # #[tokio::main]
779
    /// # async fn main() -> Result<()>{
780
    /// #
781
    /// # // create a serde/json transport over tcp.
782
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
783
    /// #
784
    /// # // create an rpc client using the transport.
785
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
786
    /// #
787
    /// // Defines cookie hint
788
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
789
    /// #
790
    /// # // load the cookie file from disk and assign it to a token
791
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
792
    /// #
793
    /// // query neptune-core server to get sum of confirmed unspent UTXO
794
    /// let confirmed_available_balance = client.confirmed_available_balance(context::current(), token).await??;
795
    /// # Ok(())
796
    /// # }
797
    /// ```
798
    async fn confirmed_available_balance(token: rpc_auth::Token)
799
        -> RpcResult<NativeCurrencyAmount>;
800

801
    /// Get sum of unconfirmed, unspent available UTXOs
802
    /// includes mempool transactions, excludes time-locked utxos
803
    /// ```no_run
804
    /// # use anyhow::Result;
805
    /// # use neptune_cash::rpc_server::RPCClient;
806
    /// # use neptune_cash::rpc_auth;
807
    /// # use tarpc::tokio_serde::formats::Json;
808
    /// # use tarpc::serde_transport::tcp;
809
    /// # use tarpc::client;
810
    /// # use tarpc::context;
811
    /// #
812
    /// # #[tokio::main]
813
    /// # async fn main() -> Result<()>{
814
    /// #
815
    /// # // create a serde/json transport over tcp.
816
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
817
    /// #
818
    /// # // create an rpc client using the transport.
819
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
820
    /// #
821
    /// # // Defines cookie hint
822
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
823
    /// #
824
    /// # // load the cookie file from disk and assign it to a token
825
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
826
    /// #
827
    /// // query neptune-core server to get sum of unconfirmed unspent UTXOs
828
    /// let unconfirmed_available_balance = client.unconfirmed_available_balance(context::current(), token).await??;
829
    /// # Ok(())
830
    /// # }
831
    /// ```
832
    async fn unconfirmed_available_balance(
833
        token: rpc_auth::Token,
834
    ) -> RpcResult<NativeCurrencyAmount>;
835

836
    /// Get the client's wallet transaction history
837
    ///
838
    /// ```no_run
839
    /// # use anyhow::Result;
840
    /// # use neptune_cash::rpc_server::RPCClient;
841
    /// # use neptune_cash::rpc_auth;
842
    /// # use tarpc::tokio_serde::formats::Json;
843
    /// # use tarpc::serde_transport::tcp;
844
    /// # use tarpc::client;
845
    /// # use tarpc::context;
846
    /// #
847
    /// # #[tokio::main]
848
    /// # async fn main() -> Result<()>{
849
    /// #
850
    /// # // create a serde/json transport over tcp.
851
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
852
    /// #
853
    /// # // create an rpc client using the transport.
854
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
855
    /// #
856
    /// # // Defines cookie hint
857
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
858
    /// #
859
    /// # // load the cookie file from disk and assign it to a token
860
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
861
    /// #
862
    /// // query neptune-core server to get history of transactions, a vec containing digest, block height, timestamp and neptune coins tuples.
863
    /// let history_transactions = client.history(context::current(), token).await??;
864
    /// # Ok(())
865
    /// # }
866
    /// ```
867
    async fn history(
868
        token: rpc_auth::Token,
869
    ) -> RpcResult<Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)>>;
870

871
    /// Return information about funds in the wallet
872
    ///
873
    /// ```no_run
874
    /// # use anyhow::Result;
875
    /// # use neptune_cash::rpc_server::RPCClient;
876
    /// # use neptune_cash::rpc_auth;
877
    /// # use tarpc::tokio_serde::formats::Json;
878
    /// # use tarpc::serde_transport::tcp;
879
    /// # use tarpc::client;
880
    /// # use tarpc::context;
881
    /// #
882
    /// # #[tokio::main]
883
    /// # async fn main() -> Result<()>{
884
    /// #
885
    /// # // create a serde/json transport over tcp.
886
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
887
    /// #
888
    /// # // create an rpc client using the transport.
889
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
890
    /// #
891
    /// # // Defines cookie hint
892
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
893
    /// #
894
    /// # // load the cookie file from disk and assign it to a token
895
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
896
    /// #
897
    /// // query neptune-core server to get the funds in the wallet
898
    /// let wallet_status = client.wallet_status(context::current(), token).await??;
899
    /// # Ok(())
900
    /// # }
901
    /// ```
902
    async fn wallet_status(token: rpc_auth::Token) -> RpcResult<WalletStatus>;
903

904
    /// Return the number of expected UTXOs, including already received UTXOs.
905
    ///
906
    /// ```no_run
907
    /// # use anyhow::Result;
908
    /// # use neptune_cash::rpc_server::RPCClient;
909
    /// # use neptune_cash::rpc_auth;
910
    /// # use tarpc::tokio_serde::formats::Json;
911
    /// # use tarpc::serde_transport::tcp;
912
    /// # use tarpc::client;
913
    /// # use tarpc::context;
914
    /// #
915
    /// # #[tokio::main]
916
    /// # async fn main() -> Result<()>{
917
    /// #
918
    /// # // create a serde/json transport over tcp.
919
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
920
    /// #
921
    /// # // create an rpc client using the transport.
922
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
923
    /// #
924
    /// # // Defines cookie hint
925
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
926
    /// #
927
    /// # // load the cookie file from disk and assign it to a token
928
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
929
    /// #
930
    /// // query neptune-core server to get the number of expected utxos including already received ones.
931
    /// let wallet_status = client.num_expected_utxos(context::current(), token).await??;
932
    /// # Ok(())
933
    /// # }
934
    /// ```
935
    async fn num_expected_utxos(token: rpc_auth::Token) -> RpcResult<u64>;
936

937
    /// Return an address that this client can receive funds on
938
    /// Return the number of expected UTXOs, including already received UTXOs.
939
    ///
940
    /// ```no_run
941
    /// # use anyhow::Result;
942
    /// use neptune_cash::models::state::wallet::address::KeyType;
943
    /// # use neptune_cash::rpc_server::RPCClient;
944
    /// # use neptune_cash::rpc_auth;
945
    /// # use tarpc::tokio_serde::formats::Json;
946
    /// # use tarpc::serde_transport::tcp;
947
    /// # use tarpc::client;
948
    /// # use tarpc::context;
949
    /// #
950
    /// # #[tokio::main]
951
    /// # async fn main() -> Result<()>{
952
    /// #
953
    /// # // create a serde/json transport over tcp.
954
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
955
    /// #
956
    /// # // create an rpc client using the transport.
957
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
958
    /// #
959
    /// # // Defines cookie hint
960
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
961
    /// #
962
    /// # // load the cookie file from disk and assign it to a token
963
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
964
    /// #
965
    /// // set cryptographic key type for receiving funds
966
    /// let key_type = KeyType::Symmetric;
967
    ///
968
    /// // query neptune-core server to get a receiving address
969
    /// let wallet_status = client.next_receiving_address(context::current(), token, key_type).await??;
970
    /// # Ok(())
971
    /// # }
972
    /// ```
973
    async fn next_receiving_address(
974
        token: rpc_auth::Token,
975
        key_type: KeyType,
976
    ) -> RpcResult<Option<ReceivingAddress>>;
977

978
    /// Return all known keys, for every [KeyType]
979
    ///
980
    /// ```no_run
981
    /// # use anyhow::Result;
982
    /// # use neptune_cash::rpc_server::RPCClient;
983
    /// # use neptune_cash::rpc_auth;
984
    /// # use tarpc::tokio_serde::formats::Json;
985
    /// # use tarpc::serde_transport::tcp;
986
    /// # use tarpc::client;
987
    /// # use tarpc::context;
988
    /// #
989
    /// # #[tokio::main]
990
    /// # async fn main() -> Result<()>{
991
    /// #
992
    /// # // create a serde/json transport over tcp.
993
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
994
    /// #
995
    /// # // create an rpc client using the transport.
996
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
997
    /// #
998
    /// # // Defines cookie hint
999
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1000
    /// #
1001
    /// # // load the cookie file from disk and assign it to a token
1002
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1003
    /// #
1004
    /// // query neptune-core server to get all known keys for every [KeyType]
1005
    /// let known_keys = client.known_keys(context::current(), token ).await??;
1006
    /// # Ok(())
1007
    /// # }
1008
    /// ```
1009
    async fn known_keys(token: rpc_auth::Token) -> RpcResult<Vec<SpendingKey>>;
1010

1011
    /// Return known keys for the provided [KeyType]
1012
    ///
1013
    /// ```no_run
1014
    /// # use anyhow::Result;
1015
    /// use neptune_cash::models::state::wallet::address::KeyType;
1016
    /// # use neptune_cash::rpc_server::RPCClient;
1017
    /// # use neptune_cash::rpc_auth;
1018
    /// # use tarpc::tokio_serde::formats::Json;
1019
    /// # use tarpc::serde_transport::tcp;
1020
    /// # use tarpc::client;
1021
    /// # use tarpc::context;
1022
    /// #
1023
    /// # #[tokio::main]
1024
    /// # async fn main() -> Result<()>{
1025
    /// #
1026
    /// # // create a serde/json transport over tcp.
1027
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1028
    /// #
1029
    /// # // create an rpc client using the transport.
1030
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1031
    /// #
1032
    /// # // Defines cookie hint
1033
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1034
    /// #
1035
    /// # // load the cookie file from disk and assign it to a token
1036
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1037
    /// #
1038
    /// // set a key type
1039
    /// let key_type = KeyType::Symmetric;
1040
    ///
1041
    /// // query neptune-core server to get all known keys by [KeyType]
1042
    /// let known_keys_by_keytype = client.known_keys_by_keytype(context::current(), token, key_type ).await??;
1043
    /// # Ok(())
1044
    /// # }
1045
    /// ```
1046
    async fn known_keys_by_keytype(
1047
        token: rpc_auth::Token,
1048
        key_type: KeyType,
1049
    ) -> RpcResult<Vec<SpendingKey>>;
1050

1051
    /// Return the number of transactions in the mempool
1052
    ///
1053
    /// ```no_run
1054
    /// # use anyhow::Result;
1055
    /// # use neptune_cash::rpc_server::RPCClient;
1056
    /// # use neptune_cash::rpc_auth;
1057
    /// # use tarpc::tokio_serde::formats::Json;
1058
    /// # use tarpc::serde_transport::tcp;
1059
    /// # use tarpc::client;
1060
    /// # use tarpc::context;
1061
    /// #
1062
    /// # #[tokio::main]
1063
    /// # async fn main() -> Result<()>{
1064
    /// #
1065
    /// # // create a serde/json transport over tcp.
1066
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1067
    /// #
1068
    /// # // create an rpc client using the transport.
1069
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1070
    /// #
1071
    /// # // Defines cookie hint
1072
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1073
    /// #
1074
    /// # // load the cookie file from disk and assign it to a token
1075
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1076
    /// #
1077
    /// // query neptune-core server to get the number of transactions in the mempool
1078
    /// let mempool_tx_count = client.mempool_tx_count(context::current(), token ).await??;
1079
    /// # Ok(())
1080
    /// # }
1081
    /// ```
1082
    async fn mempool_tx_count(token: rpc_auth::Token) -> RpcResult<usize>;
1083

1084
    // TODO: Change to return current size and max size
1085
    async fn mempool_size(token: rpc_auth::Token) -> RpcResult<usize>;
1086

1087
    /// Return info about the transactions in the mempool
1088
    ///
1089
    /// ```no_run
1090
    /// # use anyhow::Result;
1091
    /// # use neptune_cash::rpc_server::RPCClient;
1092
    /// # use neptune_cash::rpc_auth;
1093
    /// # use tarpc::tokio_serde::formats::Json;
1094
    /// # use tarpc::serde_transport::tcp;
1095
    /// # use tarpc::client;
1096
    /// # use tarpc::context;
1097
    /// #
1098
    /// # #[tokio::main]
1099
    /// # async fn main() -> Result<()>{
1100
    /// #
1101
    /// # // create a serde/json transport over tcp.
1102
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1103
    /// #
1104
    /// # // create an rpc client using the transport.
1105
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1106
    /// #
1107
    /// # // Defines cookie hint
1108
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1109
    /// #
1110
    /// # // load the cookie file from disk and assign it to a token
1111
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1112
    /// #
1113
    /// // index to start from in the mempool
1114
    /// let start_index : usize = 37;
1115
    ///
1116
    /// // number of transactions
1117
    /// let number : usize = 8;
1118
    ///
1119
    /// // query neptune-core server to get the info of transactions in the mempool
1120
    /// let mempool_overview = client.mempool_overview(context::current(), token, start_index, number ).await??;
1121
    /// # Ok(())
1122
    /// # }
1123
    /// ```
1124
    async fn mempool_overview(
1125
        token: rpc_auth::Token,
1126
        start_index: usize,
1127
        number: usize,
1128
    ) -> RpcResult<Vec<MempoolTransactionInfo>>;
1129

1130
    /// Return the information used on the dashboard's overview tab
1131
    ///
1132
    /// ```no_run
1133
    /// # use anyhow::Result;
1134
    /// # use neptune_cash::rpc_server::RPCClient;
1135
    /// # use neptune_cash::rpc_auth;
1136
    /// # use tarpc::tokio_serde::formats::Json;
1137
    /// # use tarpc::serde_transport::tcp;
1138
    /// # use tarpc::client;
1139
    /// # use tarpc::context;
1140
    /// #
1141
    /// # #[tokio::main]
1142
    /// # async fn main() -> Result<()>{
1143
    /// #
1144
    /// # // create a serde/json transport over tcp.
1145
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1146
    /// #
1147
    /// # // create an rpc client using the transport.
1148
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1149
    /// #
1150
    /// # // Defines cookie hint
1151
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1152
    /// #
1153
    /// # // load the cookie file from disk and assign it to a token
1154
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1155
    /// #
1156
    /// // query neptune-core server to get the info used on dashboard overview tab
1157
    /// let dashboard_data = client.dashboard_overview_data(context::current(), token).await??;
1158
    /// # Ok(())
1159
    /// # }
1160
    /// ```
1161
    async fn dashboard_overview_data(
1162
        token: rpc_auth::Token,
1163
    ) -> RpcResult<DashBoardOverviewDataFromClient>;
1164

1165
    /// Determine whether the user-supplied string is a valid address
1166
    ///
1167
    /// ```no_run
1168
    /// # use anyhow::Result;
1169
    /// use neptune_cash::config_models::network::Network;
1170
    /// # use neptune_cash::rpc_server::RPCClient;
1171
    /// # use neptune_cash::rpc_auth;
1172
    /// # use tarpc::tokio_serde::formats::Json;
1173
    /// # use tarpc::serde_transport::tcp;
1174
    /// # use tarpc::client;
1175
    /// # use tarpc::context;
1176
    /// #
1177
    /// # #[tokio::main]
1178
    /// # async fn main() -> Result<()>{
1179
    /// #
1180
    /// # // create a serde/json transport over tcp.
1181
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1182
    /// #
1183
    /// # // create an rpc client using the transport.
1184
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1185
    /// #
1186
    /// # // Defines cookie hint
1187
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1188
    /// #
1189
    /// # // load the cookie file from disk and assign it to a token
1190
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1191
    /// #
1192
    /// // address to validate
1193
    /// let address : String = "0x484389349834834DF23".to_string();
1194
    ///
1195
    /// // network type
1196
    /// let network : Network = Network::Main;
1197
    ///
1198
    /// // query neptune-core server to check if the supplied address is valid
1199
    /// let is_valid_address = client.validate_address(context::current(), token, address, network).await??;
1200
    /// # Ok(())
1201
    /// # }
1202
    /// ```
1203
    async fn validate_address(
1204
        token: rpc_auth::Token,
1205
        address: String,
1206
        network: Network,
1207
    ) -> RpcResult<Option<ReceivingAddress>>;
1208

1209
    /// Determine whether the user-supplied string is a valid amount
1210
    ///
1211
    /// ```no_run
1212
    /// # use anyhow::Result;
1213
    /// # use neptune_cash::rpc_server::RPCClient;
1214
    /// # use neptune_cash::rpc_auth;
1215
    /// # use tarpc::tokio_serde::formats::Json;
1216
    /// # use tarpc::serde_transport::tcp;
1217
    /// # use tarpc::client;
1218
    /// # use tarpc::context;
1219
    /// #
1220
    /// # #[tokio::main]
1221
    /// # async fn main() -> Result<()>{
1222
    /// #
1223
    /// # // create a serde/json transport over tcp.
1224
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1225
    /// #
1226
    /// # // create an rpc client using the transport.
1227
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1228
    /// #
1229
    /// # // Defines cookie hint
1230
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1231
    /// #
1232
    /// # // load the cookie file from disk and assign it to a token
1233
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1234
    /// #
1235
    /// // address to validate
1236
    /// let amount : String = "132".to_string();
1237
    ///
1238
    /// // query neptune-core server to determine if the amount is valid
1239
    /// let is_valid_address = client.validate_amount(context::current(), token, amount ).await??;
1240
    /// # Ok(())
1241
    /// # }
1242
    /// ```
1243
    async fn validate_amount(
1244
        token: rpc_auth::Token,
1245
        amount: String,
1246
    ) -> RpcResult<Option<NativeCurrencyAmount>>;
1247

1248
    /// Determine whether the given amount is less than (or equal to) the balance
1249
    ///
1250
    /// ```no_run
1251
    /// # use anyhow::Result;
1252
    /// use neptune_cash::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
1253
    /// # use neptune_cash::rpc_server::RPCClient;
1254
    /// # use neptune_cash::rpc_auth;
1255
    /// # use tarpc::tokio_serde::formats::Json;
1256
    /// # use tarpc::serde_transport::tcp;
1257
    /// # use tarpc::client;
1258
    /// # use tarpc::context;
1259
    /// #
1260
    /// # #[tokio::main]
1261
    /// # async fn main() -> Result<()>{
1262
    /// #
1263
    /// # // create a serde/json transport over tcp.
1264
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1265
    /// #
1266
    /// # // create an rpc client using the transport.
1267
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1268
    /// #
1269
    /// # // Defines cookie hint
1270
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1271
    /// #
1272
    /// # // load the cookie file from disk and assign it to a token
1273
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1274
    /// #
1275
    /// // setting the amount to 47
1276
    /// let amount : NativeCurrencyAmount = NativeCurrencyAmount::coins(47);
1277
    ///
1278
    /// // query neptune-core server to determine if the amount is less than or equal to the balance
1279
    /// let amount_less_or_equals_balance = client.amount_leq_confirmed_available_balance(context::current(), token, amount ).await??;
1280
    /// # Ok(())
1281
    /// # }
1282
    /// ```
1283
    async fn amount_leq_confirmed_available_balance(
1284
        token: rpc_auth::Token,
1285
        amount: NativeCurrencyAmount,
1286
    ) -> RpcResult<bool>;
1287

1288
    /// Generate a report of all owned and unspent coins, whether time-locked or not.
1289
    ///
1290
    /// ```no_run
1291
    /// # use anyhow::Result;
1292
    /// # use neptune_cash::rpc_server::RPCClient;
1293
    /// # use neptune_cash::rpc_auth;
1294
    /// # use tarpc::tokio_serde::formats::Json;
1295
    /// # use tarpc::serde_transport::tcp;
1296
    /// # use tarpc::client;
1297
    /// # use tarpc::context;
1298
    /// #
1299
    /// # #[tokio::main]
1300
    /// # async fn main() -> Result<()>{
1301
    /// #
1302
    /// # // create a serde/json transport over tcp.
1303
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1304
    /// #
1305
    /// # // create an rpc client using the transport.
1306
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1307
    /// #
1308
    /// # // Defines cookie hint
1309
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1310
    /// #
1311
    /// # // load the cookie file from disk and assign it to a token
1312
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1313
    /// #
1314
    /// // query neptune-core server to get the list of owned and unspent coins
1315
    /// let own_coins = client.list_own_coins(context::current(), token ).await??;
1316
    /// # Ok(())
1317
    /// # }
1318
    /// ```
1319
    async fn list_own_coins(token: rpc_auth::Token) -> RpcResult<Vec<CoinWithPossibleTimeLock>>;
1320

1321
    /// Get CPU temperature.
1322
    ///
1323
    /// ```no_run
1324
    /// # use anyhow::Result;
1325
    /// # use neptune_cash::rpc_server::RPCClient;
1326
    /// # use neptune_cash::rpc_auth;
1327
    /// # use tarpc::tokio_serde::formats::Json;
1328
    /// # use tarpc::serde_transport::tcp;
1329
    /// # use tarpc::client;
1330
    /// # use tarpc::context;
1331
    /// #
1332
    /// # #[tokio::main]
1333
    /// # async fn main() -> Result<()>{
1334
    /// #
1335
    /// # // create a serde/json transport over tcp.
1336
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1337
    /// #
1338
    /// # // create an rpc client using the transport.
1339
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1340
    /// #
1341
    /// # // Defines cookie hint
1342
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1343
    /// #
1344
    /// # // load the cookie file from disk and assign it to a token
1345
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1346
    /// #
1347
    /// // query neptune-core server instance to get its CPU temperature
1348
    /// let cpu_temperature = client.cpu_temp(context::current(), token ).await??;
1349
    /// # Ok(())
1350
    /// # }
1351
    /// ```
1352
    async fn cpu_temp(token: rpc_auth::Token) -> RpcResult<Option<f32>>;
1353

1354
    /// Get the proof-of-work puzzle for the current block proposal. Uses the
1355
    /// node's secret key to populate the guesser digest.
1356
    ///
1357
    /// Returns `None` if no block proposal for the next block is known yet.
1358
    async fn pow_puzzle_internal_key(
1359
        token: rpc_auth::Token,
1360
    ) -> RpcResult<Option<ProofOfWorkPuzzle>>;
1361

1362
    /// Get the proof-of-work puzzle for the current block proposal. Like
1363
    /// [Self::pow_puzzle_internal_key] but returned puzzle uses an externally
1364
    /// provided digest to populate the guesser digest field in the block
1365
    /// header, meaning that this client cannot claim the reward in case a
1366
    /// valid PoW-solution is found. This endpoint allows for "cold" guessing
1367
    /// where the node does not hold the key to spend the guesser reward.
1368
    ///
1369
    /// Returns `None` if no block proposal for the next block is known yet.
1370
    async fn pow_puzzle_external_key(
1371
        token: rpc_auth::Token,
1372
        guesser_digest: Digest,
1373
    ) -> RpcResult<Option<ProofOfWorkPuzzle>>;
1374

1375
    /******** BLOCKCHAIN STATISTICS ********/
1376
    // Place all endpoints that relate to statistics of the blockchain here
1377

1378
    /// Return the block intervals of a range of blocks. Return value is the
1379
    /// number of milliseconds it took to mine the (canonical) block with the
1380
    /// specified height. Does not include the interval between genesis block
1381
    /// and block 1 since genesis block was not actually mined and its timestamp
1382
    /// doesn't carry the same meaning as those of later blocks.
1383
    async fn block_intervals(
1384
        token: rpc_auth::Token,
1385
        last_block: BlockSelector,
1386
        max_num_blocks: Option<usize>,
1387
    ) -> RpcResult<Option<Vec<(u64, u64)>>>;
1388

1389
    /// Return the difficulties of a range of blocks.
1390
    ///
1391
    /// ```no_run
1392
    /// # use anyhow::Result;
1393
    /// use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
1394
    /// # use neptune_cash::rpc_server::RPCClient;
1395
    /// # use neptune_cash::rpc_auth;
1396
    /// # use tarpc::tokio_serde::formats::Json;
1397
    /// # use tarpc::serde_transport::tcp;
1398
    /// # use tarpc::client;
1399
    /// # use tarpc::context;
1400
    /// #
1401
    /// # #[tokio::main]
1402
    /// # async fn main() -> Result<()>{
1403
    /// #
1404
    /// # // create a serde/json transport over tcp.
1405
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1406
    /// #
1407
    /// # // create an rpc client using the transport.
1408
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1409
    /// #
1410
    /// # // Defines cookie hint
1411
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1412
    /// #
1413
    /// # // load the cookie file from disk and assign it to a token
1414
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1415
    /// #
1416
    /// // sets the last block
1417
    /// let last_block : BlockSelector = BlockSelector::Genesis;
1418
    ///
1419
    /// // set maximum number of blocks to 5 blocks
1420
    /// let max_num_blocks : Option<usize> = Some(5);
1421
    ///
1422
    /// // query neptune-core server to get difficulties of a range of blocks
1423
    /// let block_difficulties = client.block_difficulties(context::current(), token, last_block, max_num_blocks).await??;
1424
    /// # Ok(())
1425
    /// # }
1426
    /// ```
1427
    async fn block_difficulties(
1428
        token: rpc_auth::Token,
1429
        last_block: BlockSelector,
1430
        max_num_blocks: Option<usize>,
1431
    ) -> RpcResult<Vec<(u64, Difficulty)>>;
1432

1433
    /******** PEER INTERACTIONS ********/
1434

1435
    /// Broadcast transaction notifications for all transactions in this node's
1436
    /// mempool.
1437
    async fn broadcast_all_mempool_txs(token: rpc_auth::Token) -> RpcResult<()>;
1438

1439
    /******** CHANGE THINGS ********/
1440
    // Place all things that change state here
1441

1442
    /// Clears standing for all peers, connected or not
1443
    ///
1444
    /// ```no_run
1445
    /// # use anyhow::Result;
1446
    /// # use neptune_cash::rpc_server::RPCClient;
1447
    /// # use neptune_cash::rpc_auth;
1448
    /// # use tarpc::tokio_serde::formats::Json;
1449
    /// # use tarpc::serde_transport::tcp;
1450
    /// # use tarpc::client;
1451
    /// # use tarpc::context;
1452
    /// #
1453
    /// # #[tokio::main]
1454
    /// # async fn main() -> Result<()>{
1455
    /// #
1456
    /// # // create a serde/json transport over tcp.
1457
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1458
    /// #
1459
    /// # // create an rpc client using the transport.
1460
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1461
    /// #
1462
    /// # // Defines cookie hint
1463
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1464
    /// #
1465
    /// # // load the cookie file from disk and assign it to a token
1466
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1467
    /// #
1468
    /// // neptune-core server clears standing for all peers that are connected or not
1469
    /// let _ = client.clear_all_standings(context::current(), token).await??;
1470
    /// # Ok(())
1471
    /// # }
1472
    /// ```
1473
    async fn clear_all_standings(token: rpc_auth::Token) -> RpcResult<()>;
1474

1475
    /// Clears standing for ip, whether connected or not
1476
    ///
1477
    /// ```no_run
1478
    /// # use anyhow::Result;
1479
    /// # use neptune_cash::rpc_server::RPCClient;
1480
    /// # use neptune_cash::rpc_auth;
1481
    /// # use tarpc::tokio_serde::formats::Json;
1482
    /// # use tarpc::serde_transport::tcp;
1483
    /// # use tarpc::client;
1484
    /// # use tarpc::context;
1485
    /// # use std::net::{IpAddr, Ipv4Addr};
1486
    /// #
1487
    /// # #[tokio::main]
1488
    /// # async fn main() -> Result<()>{
1489
    /// #
1490
    /// # // create a serde/json transport over tcp.
1491
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1492
    /// #
1493
    /// // create an rpc client using the transport.
1494
    /// let client = RPCClient::new(client::Config::default(), transport).spawn();
1495
    ///
1496
    /// // Defines cookie hint
1497
    /// let cookie_hint = client.cookie_hint(context::current()).await??;
1498
    ///
1499
    /// // load the cookie file from disk and assign it to a token
1500
    /// let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1501
    ///
1502
    /// // IP address 87.23.90.12 to clear standing
1503
    /// let ip = IpAddr::V4(Ipv4Addr::new(87, 23, 90, 12));
1504
    ///
1505
    /// // neptune-core server clears standing for all peers that are connected or not
1506
    /// let _ = client.clear_standing_by_ip(context::current(), token, ip).await??;
1507
    /// # Ok(())
1508
    /// # }
1509
    /// ```
1510
    async fn clear_standing_by_ip(token: rpc_auth::Token, ip: IpAddr) -> RpcResult<()>;
1511

1512
    /// Send coins to a single recipient.
1513
    ///
1514
    /// See docs for [send_to_many()](Self::send_to_many())
1515
    ///
1516
    /// ```no_run
1517
    /// # use anyhow::Result;
1518
    /// # use neptune_cash::config_models::network::Network;
1519
    /// # use neptune_cash::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
1520
    /// # use neptune_cash::models::state::wallet::address::ReceivingAddress;
1521
    /// # use neptune_cash::models::state::wallet::utxo_notification::UtxoNotificationMedium;
1522
    /// # use neptune_cash::rpc_server::RPCClient;
1523
    /// # use neptune_cash::rpc_auth;
1524
    /// # use tarpc::tokio_serde::formats::Json;
1525
    /// # use tarpc::serde_transport::tcp;
1526
    /// # use tarpc::client;
1527
    /// # use tarpc::context;
1528
    /// # use std::net::IpAddr;
1529
    /// #
1530
    /// # #[tokio::main]
1531
    /// # async fn main() -> Result<()>{
1532
    /// #
1533
    /// # // create a serde/json transport over tcp.
1534
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1535
    /// #
1536
    /// # // create an rpc client using the transport.
1537
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1538
    /// #
1539
    /// # // Defines cookie hint
1540
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1541
    /// #
1542
    /// # // load the cookie file from disk and assign it to a token
1543
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1544
    /// #
1545
    /// // Send amount set to 20 coins
1546
    /// let amount : NativeCurrencyAmount = NativeCurrencyAmount::coins(20);
1547
    /// // The coins receiving address
1548
    /// let address = ReceivingAddress::from_bech32m("nolgam1lf8vc5xpa4jf9vjakts632fct5q80d4m6tax39nrl8c55dta2h7n7lnkh9pmwckl0ndwc7897xwfgx5vv02xdt3099z62222wazz7tjl6umzewla9xzxyqefh2w47v4eh0xzvfsxjk6kq5u84rwwlflq7cs726ljttl6ls860te04cwpy5kk8n40qqjnps0gdp46namhsa3cqt0uc0s5e34h6s5rw2kl77uvvs4rlnn5t8wtuefsduuccwsxmk27r8d48g49swgafhj6wmvu5cx3lweqhnxgdgm7mmdq7ck6wkurw2jzl64k9u34kzgu9stgd47ljzte0hz0n2lcng83vtpf0u9f4hggw4llqsz2fqpe4096d9v5fzg7xvxg6zvr7gksq4yqgn8shepg5xsczmzz256m9c6r8zqdkzy4tk9he59ndtdkrrr8u5v6ztnvkvmy4sed7p7plm2y09sgksw6zcjayls4wl9fnqu97kyx9cdknksar7h8jetygur979rt5arcwmvp2dy3ynt6arna2yjpevt9209v9g2p5cvp6gjp9850w3w6afeg8yuhp6u447hrudcssyjauqa2p7jk4tz37wg70yrdhsgn35sc0hdkclvpapu75dgtmswk0vtgadx44mqdps6ry6005xqups9dpc93u66qj9j7lfaqgdqrrfg9pkxhjl99ge387rh257x2phfvjvc8y66p22wax8myyhm7mgmlxu9gug0km3lmn4lzcyj32mduy6msy4kfn5z2tr67zfxadnj6wc0av27mk0j90pf67uzp9ps8aekr24kpv5n3qeczfznen9vj67ft95s93t26l8uh87qr6kp8lsyuzm4h36de830h6rr3lhg5ac995nrsu6h0p56t5tnglvx0s02mr0ts95fgcevveky5kkw6zgj6jd5m3n5ljhw862km8sedr30xvg8t9vh409ufuxdnfuypvqdq49z6mp46p936pjzwwqjda6yy5wuxx9lffrxwcmfqzch6nz2l4mwd2vlsdr58vhygppy6nm6tduyemw4clwj9uac4v990xt6jt7e2al7m6sjlq4qgxfjf4ytx8f5j460vvr7yac9hsvlsat2vh5gl55mt4wr7v5p3m6k5ya5442xdarastxlmpf2vqz5lusp8tlglxkj0jksgwqgtj6j0kxwmw40egpzs5rr996xpv8wwqyja4tmw599n9fh77f5ruxk69vtpwl9z5ezmdn92cpyyhwff59ypp0z5rv98vdvm67umqzt0ljjan30u3a8nga35fdy450ht9gef24mveucxqwv5aflge5r3amxsvd7l30j9kcqm7alq0ks2wqpde7pdct2gmvafxvjg3ad0a3h58assjaszvmykl3k5tn238gstm2shlvad4a53mm5ztvp5q2zt4pdzj0ssevlkumwhc0g5cxnxc9u7rh9gffkq7h9ufcxkgtghe32sv3vwzkessr52mcmajt83lvz45wqru9hht8cytfedtjlv7z7en6pp0guja85ft3rv6hzf2e02e7wfu38s0nyfzkc2qy2k298qtmxgrpduntejtvenr80csnckajnhu44399tkm0a7wdldalf678n9prd54twwlw24xhppxqlquatfztllkeejlkfxuayddwagh6uzx040tqlcs7hcflnu0ywynmz0chz48qcx7dsc4gpseu0dqvmmezpuv0tawm78nleju2vp4lkehua56hrnuj2wuc5lqvxlnskvp53vu7e2399pgp7xcwe3ww23qcd9pywladq34nk6cwcvtj3vdfgwf6r7s6vq46y2x05e043nj6tu8am2und8z3ftf3he5ccjxamtnmxfd79m04ph36kzx6e789dhqrwmwcfrn9ulsedeplk3dvrmad6f20y9qfl6n6kzaxkmmmaq4d6s5rl4kmhc7fcdkrkandw2jxdjckuscu56syly8rtjatj4j2ug23cwvep3dgcdvmtr32296nf9vdl3rcu0r7hge23ydt83k5nhtnexuqrnamveacz6c43eay9nz4pjjwjatkgp80lg9tnf5kdr2eel8s2fk6v338x4hu00htemm5pq6qlucqqq5tchhtekjzdu50erqd2fkdu9th3wl0mqxz5u7wnpgwgpammv2yqpa5znljegyhke0dz9vg27uh5t5x6qdgf7vu54lqssejekwzfxchjyq2s8frm9fmt688w76aug56v6n3w5xdre78xplfsdw3e4j6dc5w7tf83r25re0duq6h8z54wnkqr9yh2k0skjqea4elgcr4aw7hks9m8w3tx8w9xlxpqqll2zeql55ew7e90dyuynkqxfuqzv45t22ljamdll3udvqrllprdltthzm866jdaxkkrnryj4cmc2m7sk99clgql3ynrhe9kynqn4mh3tepk8dtq7cndtc2hma29s4cuylsvg04s70uyr53w5656su5rjem5egss08zrfaef0mww6t8pr26uph2n8a2cs55ydx4xhasjqk7xs0akh6f26j2ec4d8pd0kdf4jya6p9jl48wmy5autdpw2q8mehrq6kypt573genj66l5zkq6xvrdqugmfczxa2gj9ylx3pgpjqnhuem9udfkj9qr2y8lh728sr7uaedu5wwmfa72ykh395jqh7f7f9p2gskn6u7k844kpnwe3eqv84pl53r6x9af88a8ey7298njdg03h8mxqz2x6z8ys3qpuxq768tjq0zhrnjgns8d78euzwsvx6vn4f9tftrp68zcch3h75mc9drpt7tpvnyyqfjuqclxhdwhdwtsakecv04p9r3jx90htql9a3ht5mxrj4ercv4cd52wk4qhu7dn4tqe7yclqx2l36gcsrzmdlv440qls7qjpq6k95mst485vpennnur8h62a7d7syvyer89qtyfzlfhz8a5a0x5tuwhc9mah0e944xzhsc6uvpv8vat44w7r3xyw8q85y77jux8zhndrhdn36swryffqmpkxgcw4g29q40sul4fl5vrfru08a5j3rd3jl8799srpf2xqpxq38wwvhr4mxqf5wwdqfqq7harshggvufzlgn0l9fq0j76dyuge75jmzy8celvw6wesfs82n4jw2k8jnus2zds5a67my339uuzka4w72tau6j7wyu0lla0mcjpaflphsuy7f2phev6tr8vc9nj2mczkeg4vy3n5jkgecwgrvwu3vw9x5knpkxzv8kw3dpzzxy3rvrs56vxw8ugmyz2vdj6dakjyq3feym4290l7hgdt0ac5u49sekezzf0ghwmlek4h75fkzpvuly9zupw32dd3l9my282nekgk78fe6ayjyhczetxf8r82yd2askl52kmupr9xaxw0jd08dsd3523ea6ge48384rlmt4mu4w4x0q9s", Network::Main)?;
1549
    /// // owned utxo notify method
1550
    /// let notify_self : UtxoNotificationMedium = UtxoNotificationMedium::OnChain;
1551
    /// #
1552
    /// // unwowned utxo notify method
1553
    /// let notify_other : UtxoNotificationMedium = UtxoNotificationMedium::OnChain;
1554
    /// #
1555
    /// // Max fee
1556
    /// let fee : NativeCurrencyAmount = NativeCurrencyAmount::coins(10);
1557
    /// #
1558
    /// // neptune-core server sends token to a single recipient
1559
    /// let send_result = client.send(context::current(), token, amount, address, notify_self, notify_other, fee).await??;
1560
    /// # Ok(())
1561
    /// # }
1562
    /// ```
1563
    async fn send(
1564
        token: rpc_auth::Token,
1565
        amount: NativeCurrencyAmount,
1566
        address: ReceivingAddress,
1567
        owned_utxo_notify_method: UtxoNotificationMedium,
1568
        unowned_utxo_notify_medium: UtxoNotificationMedium,
1569
        fee: NativeCurrencyAmount,
1570
    ) -> RpcResult<(TransactionKernelId, Vec<PrivateNotificationData>)>;
1571

1572
    /// Send coins to multiple recipients
1573
    ///
1574
    /// note: sending is rate-limited to 2 sends per block until block
1575
    /// 25000 is reached.
1576
    ///
1577
    /// `outputs` is a list of transaction outputs in the format
1578
    /// `[(address:amount)]`.  The address may be any type supported by
1579
    /// [ReceivingAddress].
1580
    ///
1581
    /// `owned_utxo_notify_method` specifies how our wallet will be notified of
1582
    /// any outputs destined for it. This includes the change output if one is
1583
    /// necessary. [UtxoNotificationMedium] defines `OnChain` and `OffChain` delivery
1584
    /// of notifications.
1585
    ///
1586
    /// `OffChain` delivery requires less blockchain space and may result in a
1587
    /// lower fee than `OnChain` delivery however there is more potential of
1588
    /// losing funds should the wallet files become corrupted or lost.
1589
    ///
1590
    ///  tip: if using `OnChain` notification use a
1591
    /// [ReceivingAddress::Symmetric] as the receiving address for any
1592
    /// outputs destined for your own wallet.  This happens automatically for
1593
    /// the Change output only.
1594
    ///
1595
    /// `unowned_utxo_notify_method` specifies how to notify other wallets of
1596
    /// any outputs destined for them.
1597
    ///
1598
    ///
1599
    /// `fee` represents the fee in native coins to pay the miner who mines
1600
    /// the block that initially confirms the resulting transaction.
1601
    ///
1602
    /// a [Digest] of the resulting [Transaction](crate::models::blockchain::transaction::Transaction) is returned on success, else [None].
1603
    ///
1604
    /// A list of the encoded transaction notifications is also returned. The relevant notifications
1605
    /// should be sent to the transaction receiver in case `Offchain` notifications are used.
1606
    ///
1607
    /// ```no_run
1608
    /// # use anyhow::Result;
1609
    /// # use neptune_cash::config_models::network::Network;
1610
    /// # use neptune_cash::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
1611
    /// # use neptune_cash::models::state::wallet::address::ReceivingAddress;
1612
    /// # use neptune_cash::models::state::wallet::utxo_notification::UtxoNotificationMedium;
1613
    /// # use neptune_cash::rpc_server::RPCClient;
1614
    /// # use neptune_cash::rpc_auth;
1615
    /// # use tarpc::tokio_serde::formats::Json;
1616
    /// # use tarpc::serde_transport::tcp;
1617
    /// # use tarpc::client;
1618
    /// # use tarpc::context;
1619
    /// # use std::net::IpAddr;
1620
    /// #
1621
    /// # #[tokio::main]
1622
    /// # async fn main() -> Result<()>{
1623
    /// #
1624
    /// # // create a serde/json transport over tcp.
1625
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1626
    /// #
1627
    /// # // create an rpc client using the transport.
1628
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1629
    /// #
1630
    /// # // Defines cookie hint
1631
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1632
    /// #
1633
    /// # // load the cookie file from disk and assign it to a token
1634
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1635
    /// #
1636
    /// // List of receiving addresses and the amounts to send
1637
    /// let outputs : Vec<(ReceivingAddress, NativeCurrencyAmount)> = vec![
1638
    ///     (ReceivingAddress::from_bech32m("nolgam1lf8vc5xpa4jf9vjakts632fct5q80d4m6tax39nrl8c55dta2h7n7lnkh9pmwckl0ndwc7897xwfgx5vv02xdt3099z62222wazz7tjl6umzewla9xzxyqefh2w47v4eh0xzvfsxjk6kq5u84rwwlflq7cs726ljttl6ls860te04cwpy5kk8n40qqjnps0gdp46namhsa3cqt0uc0s5e34h6s5rw2kl77uvvs4rlnn5t8wtuefsduuccwsxmk27r8d48g49swgafhj6wmvu5cx3lweqhnxgdgm7mmdq7ck6wkurw2jzl64k9u34kzgu9stgd47ljzte0hz0n2lcng83vtpf0u9f4hggw4llqsz2fqpe4096d9v5fzg7xvxg6zvr7gksq4yqgn8shepg5xsczmzz256m9c6r8zqdkzy4tk9he59ndtdkrrr8u5v6ztnvkvmy4sed7p7plm2y09sgksw6zcjayls4wl9fnqu97kyx9cdknksar7h8jetygur979rt5arcwmvp2dy3ynt6arna2yjpevt9209v9g2p5cvp6gjp9850w3w6afeg8yuhp6u447hrudcssyjauqa2p7jk4tz37wg70yrdhsgn35sc0hdkclvpapu75dgtmswk0vtgadx44mqdps6ry6005xqups9dpc93u66qj9j7lfaqgdqrrfg9pkxhjl99ge387rh257x2phfvjvc8y66p22wax8myyhm7mgmlxu9gug0km3lmn4lzcyj32mduy6msy4kfn5z2tr67zfxadnj6wc0av27mk0j90pf67uzp9ps8aekr24kpv5n3qeczfznen9vj67ft95s93t26l8uh87qr6kp8lsyuzm4h36de830h6rr3lhg5ac995nrsu6h0p56t5tnglvx0s02mr0ts95fgcevveky5kkw6zgj6jd5m3n5ljhw862km8sedr30xvg8t9vh409ufuxdnfuypvqdq49z6mp46p936pjzwwqjda6yy5wuxx9lffrxwcmfqzch6nz2l4mwd2vlsdr58vhygppy6nm6tduyemw4clwj9uac4v990xt6jt7e2al7m6sjlq4qgxfjf4ytx8f5j460vvr7yac9hsvlsat2vh5gl55mt4wr7v5p3m6k5ya5442xdarastxlmpf2vqz5lusp8tlglxkj0jksgwqgtj6j0kxwmw40egpzs5rr996xpv8wwqyja4tmw599n9fh77f5ruxk69vtpwl9z5ezmdn92cpyyhwff59ypp0z5rv98vdvm67umqzt0ljjan30u3a8nga35fdy450ht9gef24mveucxqwv5aflge5r3amxsvd7l30j9kcqm7alq0ks2wqpde7pdct2gmvafxvjg3ad0a3h58assjaszvmykl3k5tn238gstm2shlvad4a53mm5ztvp5q2zt4pdzj0ssevlkumwhc0g5cxnxc9u7rh9gffkq7h9ufcxkgtghe32sv3vwzkessr52mcmajt83lvz45wqru9hht8cytfedtjlv7z7en6pp0guja85ft3rv6hzf2e02e7wfu38s0nyfzkc2qy2k298qtmxgrpduntejtvenr80csnckajnhu44399tkm0a7wdldalf678n9prd54twwlw24xhppxqlquatfztllkeejlkfxuayddwagh6uzx040tqlcs7hcflnu0ywynmz0chz48qcx7dsc4gpseu0dqvmmezpuv0tawm78nleju2vp4lkehua56hrnuj2wuc5lqvxlnskvp53vu7e2399pgp7xcwe3ww23qcd9pywladq34nk6cwcvtj3vdfgwf6r7s6vq46y2x05e043nj6tu8am2und8z3ftf3he5ccjxamtnmxfd79m04ph36kzx6e789dhqrwmwcfrn9ulsedeplk3dvrmad6f20y9qfl6n6kzaxkmmmaq4d6s5rl4kmhc7fcdkrkandw2jxdjckuscu56syly8rtjatj4j2ug23cwvep3dgcdvmtr32296nf9vdl3rcu0r7hge23ydt83k5nhtnexuqrnamveacz6c43eay9nz4pjjwjatkgp80lg9tnf5kdr2eel8s2fk6v338x4hu00htemm5pq6qlucqqq5tchhtekjzdu50erqd2fkdu9th3wl0mqxz5u7wnpgwgpammv2yqpa5znljegyhke0dz9vg27uh5t5x6qdgf7vu54lqssejekwzfxchjyq2s8frm9fmt688w76aug56v6n3w5xdre78xplfsdw3e4j6dc5w7tf83r25re0duq6h8z54wnkqr9yh2k0skjqea4elgcr4aw7hks9m8w3tx8w9xlxpqqll2zeql55ew7e90dyuynkqxfuqzv45t22ljamdll3udvqrllprdltthzm866jdaxkkrnryj4cmc2m7sk99clgql3ynrhe9kynqn4mh3tepk8dtq7cndtc2hma29s4cuylsvg04s70uyr53w5656su5rjem5egss08zrfaef0mww6t8pr26uph2n8a2cs55ydx4xhasjqk7xs0akh6f26j2ec4d8pd0kdf4jya6p9jl48wmy5autdpw2q8mehrq6kypt573genj66l5zkq6xvrdqugmfczxa2gj9ylx3pgpjqnhuem9udfkj9qr2y8lh728sr7uaedu5wwmfa72ykh395jqh7f7f9p2gskn6u7k844kpnwe3eqv84pl53r6x9af88a8ey7298njdg03h8mxqz2x6z8ys3qpuxq768tjq0zhrnjgns8d78euzwsvx6vn4f9tftrp68zcch3h75mc9drpt7tpvnyyqfjuqclxhdwhdwtsakecv04p9r3jx90htql9a3ht5mxrj4ercv4cd52wk4qhu7dn4tqe7yclqx2l36gcsrzmdlv440qls7qjpq6k95mst485vpennnur8h62a7d7syvyer89qtyfzlfhz8a5a0x5tuwhc9mah0e944xzhsc6uvpv8vat44w7r3xyw8q85y77jux8zhndrhdn36swryffqmpkxgcw4g29q40sul4fl5vrfru08a5j3rd3jl8799srpf2xqpxq38wwvhr4mxqf5wwdqfqq7harshggvufzlgn0l9fq0j76dyuge75jmzy8celvw6wesfs82n4jw2k8jnus2zds5a67my339uuzka4w72tau6j7wyu0lla0mcjpaflphsuy7f2phev6tr8vc9nj2mczkeg4vy3n5jkgecwgrvwu3vw9x5knpkxzv8kw3dpzzxy3rvrs56vxw8ugmyz2vdj6dakjyq3feym4290l7hgdt0ac5u49sekezzf0ghwmlek4h75fkzpvuly9zupw32dd3l9my282nekgk78fe6ayjyhczetxf8r82yd2askl52kmupr9xaxw0jd08dsd3523ea6ge48384rlmt4mu4w4x0q9s", Network::Main)?, NativeCurrencyAmount::coins(20)),
1639
    ///     (ReceivingAddress::from_bech32m("nolgam1ld9vc5xpa4jf9vjakts632fct5q80d4m6tax39nrl8c55dta2h7n7lnkh9pmwckl0ndwc7897xwfgx5vv02xdt3099z62222wazz7tjl6umzewla9xzxyqefh2w47v4eh0xzvfsxjk6kq5u84rwwlflq7cs726ljttl6ls860te04cwpy5kk8n40qqjnps0gdp46namhsa3cqt0uc0s5e34h6s5rw2kl77uvvs4rlnn5t8wtuefsduuccwsxmk27r8d48g49swgafhj6wmvu5cx3lweqhnxgdgm7mmdq7ck6wkurw2jzl64k9u34kzgu9stgd47ljzte0hz0n2lcng83vtpf0u9f4hggw4llqsz2fqpe4096d9v5fzg7xvxg6zvr7gksq4yqgn8shepg5xsczmzz256m9c6r8zqdkzy4tk9he59ndtdkrrr8u5v6ztnvkvmy4sed7p7plm2y09sgksw6zcjayls4wl9fnqu97kyx9cdknksar7h8jetygur979rt5arcwmvp2dy3ynt6arna2yjpevt9209v9g2p5cvp6gjp9850w3w6afeg8yuhp6u447hrudcssyjauqa2p7jk4tz37wg70yrdhsgn35sc0hdkclvpapu75dgtmswk0vtgadx44mqdps6ry6005xqups9dpc93u66qj9j7lfaqgdqrrfg9pkxhjl99ge387rh257x2phfvjvc8y66p22wax8myyhm7mgmlxu9gug0km3lmn4lzcyj32mduy6msy4kfn5z2tr67zfxadnj6wc0av27mk0j90pf67uzp9ps8aekr24kpv5n3qeczfznen9vj67ft95s93t26l8uh87qr6kp8lsyuzm4h36de830h6rr3lhg5ac995nrsu6h0p56t5tnglvx0s02mr0ts95fgcevveky5kkw6zgj6jd5m3n5ljhw862km8sedr30xvg8t9vh409ufuxdnfuypvqdq49z6mp46p936pjzwwqjda6yy5wuxx9lffrxwcmfqzch6nz2l4mwd2vlsdr58vhygppy6nm6tduyemw4clwj9uac4v990xt6jt7e2al7m6sjlq4qgxfjf4ytx8f5j460vvr7yac9hsvlsat2vh5gl55mt4wr7v5p3m6k5ya5442xdarastxlmpf2vqz5lusp8tlglxkj0jksgwqgtj6j0kxwmw40egpzs5rr996xpv8wwqyja4tmw599n9fh77f5ruxk69vtpwl9z5ezmdn92cpyyhwff59ypp0z5rv98vdvm67umqzt0ljjan30u3a8nga35fdy450ht9gef24mveucxqwv5aflge5r3amxsvd7l30j9kcqm7alq0ks2wqpde7pdct2gmvafxvjg3ad0a3h58assjaszvmykl3k5tn238gstm2shlvad4a53mm5ztvp5q2zt4pdzj0ssevlkumwhc0g5cxnxc9u7rh9gffkq7h9ufcxkgtghe32sv3vwzkessr52mcmajt83lvz45wqru9hht8cytfedtjlv7z7en6pp0guja85ft3rv6hzf2e02e7wfu38s0nyfzkc2qy2k298qtmxgrpduntejtvenr80csnckajnhu44399tkm0a7wdldalf678n9prd54twwlw24xhppxqlquatfztllkeejlkfxuayddwagh6uzx040tqlcs7hcflnu0ywynmz0chz48qcx7dsc4gpseu0dqvmmezpuv0tawm78nleju2vp4lkehua56hrnuj2wuc5lqvxlnskvp53vu7e2399pgp7xcwe3ww23qcd9pywladq34nk6cwcvtj3vdfgwf6r7s6vq46y2x05e043nj6tu8am2und8z3ftf3he5ccjxamtnmxfd79m04ph36kzx6e789dhqrwmwcfrn9ulsedeplk3dvrmad6f20y9qfl6n6kzaxkmmmaq4d6s5rl4kmhc7fcdkrkandw2jxdjckuscu56syly8rtjatj4j2ug23cwvep3dgcdvmtr32296nf9vdl3rcu0r7hge23ydt83k5nhtnexuqrnamveacz6c43eay9nz4pjjwjatkgp80lg9tnf5kdr2eel8s2fk6v338x4hu00htemm5pq6qlucqqq5tchhtekjzdu50erqd2fkdu9th3wl0mqxz5u7wnpgwgpammv2yqpa5znljegyhke0dz9vg27uh5t5x6qdgf7vu54lqssejekwzfxchjyq2s8frm9fmt688w76aug56v6n3w5xdre78xplfsdw3e4j6dc5w7tf83r25re0duq6h8z54wnkqr9yh2k0skjqea4elgcr4aw7hks9m8w3tx8w9xlxpqqll2zeql55ew7e90dyuynkqxfuqzv45t22ljamdll3udvqrllprdltthzm866jdaxkkrnryj4cmc2m7sk99clgql3ynrhe9kynqn4mh3tepk8dtq7cndtc2hma29s4cuylsvg04s70uyr53w5656su5rjem5egss08zrfaef0mww6t8pr26uph2n8a2cs55ydx4xhasjqk7xs0akh6f26j2ec4d8pd0kdf4jya6p9jl48wmy5autdpw2q8mehrq6kypt573genj66l5zkq6xvrdqugmfczxa2gj9ylx3pgpjqnhuem9udfkj9qr2y8lh728sr7uaedu5wwmfa72ykh395jqh7f7f9p2gskn6u7k844kpnwe3eqv84pl53r6x9af88a8ey7298njdg03h8mxqz2x6z8ys3qpuxq768tjq0zhrnjgns8d78euzwsvx6vn4f9tftrp68zcch3h75mc9drpt7tpvnyyqfjuqclxhdwhdwtsakecv04p9r3jx90htql9a3ht5mxrj4ercv4cd52wk4qhu7dn4tqe7yclqx2l36gcsrzmdlv440qls7qjpq6k95mst485vpennnur8h62a7d7syvyer89qtyfzlfhz8a5a0x5tuwhc9mah0e944xzhsc6uvpv8vat44w7r3xyw8q85y77jux8zhndrhdn36swryffqmpkxgcw4g29q40sul4fl5vrfru08a5j3rd3jl8799srpf2xqpxq38wwvhr4mxqf5wwdqfqq7harshggvufzlgn0l9fq0j76dyuge75jmzy8celvw6wesfs82n4jw2k8jnus2zds5a67my339uuzka4w72tau6j7wyu0lla0mcjpaflphsuy7f2phev6tr8vc9nj2mczkeg4vy3n5jkgecwgrvwu3vw9x5knpkxzv8kw3dpzzxy3rvrs56vxw8ugmyz2vdj6dakjyq3feym4290l7hgdt0ac5u49sekezzf0ghwmlek4h75fkzpvuly9zupw32dd3l9my282nekgk78fe6ayjyhczetxf8r82yd2askl52kmupr9xaxw0jd08dsd3523ea6ge48384rlmt4mu4w4x0q9s", Network::Main)?, NativeCurrencyAmount::coins(57)),
1640
    /// ];
1641
    /// // owned utxo notify method
1642
    /// let notify_self : UtxoNotificationMedium = UtxoNotificationMedium::OnChain;
1643
    /// #
1644
    /// // unwowned utxo notify method
1645
    /// let notify_other : UtxoNotificationMedium = UtxoNotificationMedium::OnChain;
1646
    /// #
1647
    /// // Max fee
1648
    /// let fee : NativeCurrencyAmount = NativeCurrencyAmount::coins(10);
1649
    /// #
1650
    /// // neptune-core server sends token to a single recipient
1651
    /// let send_result = client.send_to_many(context::current(), token, outputs, notify_self, notify_other, fee).await??;
1652
    /// # Ok(())
1653
    /// # }
1654
    /// ```
1655
    async fn send_to_many(
1656
        token: rpc_auth::Token,
1657
        outputs: Vec<(ReceivingAddress, NativeCurrencyAmount)>,
1658
        owned_utxo_notify_medium: UtxoNotificationMedium,
1659
        unowned_utxo_notify_medium: UtxoNotificationMedium,
1660
        fee: NativeCurrencyAmount,
1661
    ) -> RpcResult<(TransactionKernelId, Vec<PrivateNotificationData>)>;
1662

1663
    /// claim a utxo
1664
    ///
1665
    /// The input string must be a valid bech32m encoded `UtxoTransferEncrypted`
1666
    /// for the current network and the wallet must have the corresponding
1667
    /// `SpendingKey` for decryption.
1668
    ///
1669
    /// upon success, a new `ExpectedUtxo` will be added to the local wallet
1670
    /// state.
1671
    ///
1672
    /// if the utxo has already been claimed, this call has no effect.
1673
    ///
1674
    /// Return true if a new expected UTXO was added, otherwise false.
1675
    ///
1676
    /// ```no_run
1677
    /// # use anyhow::Result;
1678
    /// # use neptune_cash::rpc_server::RPCClient;
1679
    /// # use neptune_cash::rpc_auth;
1680
    /// # use tarpc::tokio_serde::formats::Json;
1681
    /// # use tarpc::serde_transport::tcp;
1682
    /// # use tarpc::client;
1683
    /// # use tarpc::context;
1684
    /// #
1685
    /// # #[tokio::main]
1686
    /// # async fn main() -> Result<()>{
1687
    /// #
1688
    /// # // create a serde/json transport over tcp.
1689
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1690
    /// #
1691
    /// # // create an rpc client using the transport.
1692
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1693
    /// #
1694
    /// # // Defines cookie hint
1695
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1696
    /// #
1697
    /// # // load the cookie file from disk and assign it to a token
1698
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1699
    /// #
1700
    /// // Encryted value of utxo transfer
1701
    /// let utxo_transfer_encrypted = "XXXXXXX".to_string();
1702
    ///
1703
    /// // max search depth is set to 3
1704
    /// let max_search_depth : Option<u64> = Some(3);
1705
    ///
1706
    /// // claim utxo
1707
    /// let utxo_claimed = client.claim_utxo(context::current(), token, utxo_transfer_encrypted, max_search_depth).await??;
1708
    /// # Ok(())
1709
    /// # }
1710
    /// ```
1711
    async fn claim_utxo(
1712
        token: rpc_auth::Token,
1713
        utxo_transfer_encrypted: String,
1714
        max_search_depth: Option<u64>,
1715
    ) -> RpcResult<bool>;
1716

1717
    /// Stop miner if running
1718
    ///
1719
    /// ```no_run
1720
    /// # use anyhow::Result;
1721
    /// # use neptune_cash::rpc_server::RPCClient;
1722
    /// # use neptune_cash::rpc_auth;
1723
    /// # use tarpc::tokio_serde::formats::Json;
1724
    /// # use tarpc::serde_transport::tcp;
1725
    /// # use tarpc::client;
1726
    /// # use tarpc::context;
1727
    /// #
1728
    /// # #[tokio::main]
1729
    /// # async fn main() -> Result<()>{
1730
    /// #
1731
    /// # // create a serde/json transport over tcp.
1732
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1733
    /// #
1734
    /// # // create an rpc client using the transport.
1735
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1736
    /// #
1737
    /// # // Defines cookie hint
1738
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1739
    /// #
1740
    /// # // load the cookie file from disk and assign it to a token
1741
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1742
    /// #
1743
    ///  // stops miner if running
1744
    /// let _ = client.pause_miner(context::current(), token).await??;
1745
    /// # Ok(())
1746
    /// # }
1747
    /// ```
1748
    async fn pause_miner(token: rpc_auth::Token) -> RpcResult<()>;
1749

1750
    /// Start miner if not running
1751
    ///
1752
    /// ```no_run
1753
    /// # use anyhow::Result;
1754
    /// # use neptune_cash::rpc_server::RPCClient;
1755
    /// # use neptune_cash::rpc_auth;
1756
    /// # use tarpc::tokio_serde::formats::Json;
1757
    /// # use tarpc::serde_transport::tcp;
1758
    /// # use tarpc::client;
1759
    /// # use tarpc::context;
1760
    /// #
1761
    /// # #[tokio::main]
1762
    /// # async fn main() -> Result<()>{
1763
    /// #
1764
    /// # // create a serde/json transport over tcp.
1765
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1766
    /// #
1767
    /// # // create an rpc client using the transport.
1768
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1769
    /// #
1770
    /// # // Defines cookie hint
1771
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1772
    /// #
1773
    /// # // load the cookie file from disk and assign it to a token
1774
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1775
    /// #
1776
    ///  // start miner if not running
1777
    /// let _ = client.restart_miner(context::current(), token).await??;
1778
    /// # Ok(())
1779
    /// # }
1780
    /// ```
1781
    async fn restart_miner(token: rpc_auth::Token) -> RpcResult<()>;
1782

1783
    /// Provide a PoW-solution to the current block proposal.
1784
    ///
1785
    /// If the solution is considered valid by the running node, the new block
1786
    /// is broadcast to all peers on the network, and `true` is returned.
1787
    /// Otherwise the provided solution is ignored, and `false` is returned.
1788
    async fn provide_pow_solution(
1789
        token: rpc_auth::Token,
1790
        nonce: Digest,
1791
        proposal_id: Digest,
1792
    ) -> RpcResult<bool>;
1793

1794
    /// mark MUTXOs as abandoned
1795
    ///
1796
    /// ```no_run
1797
    /// # use anyhow::Result;
1798
    /// # use neptune_cash::rpc_server::RPCClient;
1799
    /// # use neptune_cash::rpc_auth;
1800
    /// # use tarpc::tokio_serde::formats::Json;
1801
    /// # use tarpc::serde_transport::tcp;
1802
    /// # use tarpc::client;
1803
    /// # use tarpc::context;
1804
    /// #
1805
    /// # #[tokio::main]
1806
    /// # async fn main() -> Result<()>{
1807
    /// #
1808
    /// # // create a serde/json transport over tcp.
1809
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1810
    /// #
1811
    /// # // create an rpc client using the transport.
1812
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1813
    /// #
1814
    /// # // Defines cookie hint
1815
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1816
    /// #
1817
    /// # // load the cookie file from disk and assign it to a token
1818
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1819
    /// #
1820
    ///  // marks mutxos as abandoned
1821
    /// let abandoned_monitored_utxos = client.prune_abandoned_monitored_utxos(context::current(), token).await??;
1822
    /// # Ok(())
1823
    /// # }
1824
    /// ```
1825
    async fn prune_abandoned_monitored_utxos(token: rpc_auth::Token) -> RpcResult<usize>;
1826

1827
    /// Gracious shutdown.
1828
    ///
1829
    /// ```no_run
1830
    /// # use anyhow::Result;
1831
    /// # use neptune_cash::rpc_server::RPCClient;
1832
    /// # use neptune_cash::rpc_auth;
1833
    /// # use tarpc::tokio_serde::formats::Json;
1834
    /// # use tarpc::serde_transport::tcp;
1835
    /// # use tarpc::client;
1836
    /// # use tarpc::context;
1837
    /// #
1838
    /// # #[tokio::main]
1839
    /// # async fn main() -> Result<()>{
1840
    /// #
1841
    /// # // create a serde/json transport over tcp.
1842
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1843
    /// #
1844
    /// # // create an rpc client using the transport.
1845
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1846
    /// #
1847
    /// # // Defines cookie hint
1848
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1849
    /// #
1850
    /// # // load the cookie file from disk and assign it to a token
1851
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1852
    /// #
1853
    ///  // shutdowns the node
1854
    /// let is_shutdown = client.shutdown(context::current(), token).await??;
1855
    /// # Ok(())
1856
    /// # }
1857
    async fn shutdown(token: rpc_auth::Token) -> RpcResult<bool>;
1858
}
1859

1860
#[derive(Clone)]
1861
pub(crate) struct NeptuneRPCServer {
1862
    pub(crate) state: GlobalStateLock,
1863
    pub(crate) rpc_server_to_main_tx: tokio::sync::mpsc::Sender<RPCServerToMain>,
1864

1865
    // copy of DataDirectory for this neptune-core instance.
1866
    data_directory: DataDirectory,
1867

1868
    // list of tokens that are valid.  RPC clients must present a token that
1869
    // matches one of these.  there should only be one of each `Token` variant
1870
    // in the list (dups ignored).
1871
    valid_tokens: Vec<rpc_auth::Token>,
1872
}
1873

1874
impl NeptuneRPCServer {
1875
    /// instantiate a new [NeptuneRPCServer]
1876
    pub fn new(
29✔
1877
        state: GlobalStateLock,
29✔
1878
        rpc_server_to_main_tx: tokio::sync::mpsc::Sender<RPCServerToMain>,
29✔
1879
        data_directory: DataDirectory,
29✔
1880
        valid_tokens: Vec<rpc_auth::Token>,
29✔
1881
    ) -> Self {
29✔
1882
        Self {
29✔
1883
            state,
29✔
1884
            valid_tokens,
29✔
1885
            rpc_server_to_main_tx,
29✔
1886
            data_directory,
29✔
1887
        }
29✔
1888
    }
29✔
1889

1890
    async fn confirmations_internal(&self, state: &GlobalState) -> Option<BlockHeight> {
1✔
1891
        match state.get_latest_balance_height().await {
1✔
1892
            Some(latest_balance_height) => {
×
1893
                let tip_block_header = state.chain.light_state().header();
×
1894

×
1895
                assert!(tip_block_header.height >= latest_balance_height);
×
1896

1897
                // subtract latest balance height from chain tip.
1898
                //
1899
                // we add 1 to the result because the block that a tx is confirmed
1900
                // in is considered the 1st confirmation.
1901
                //
1902
                // note: BlockHeight is u64 internally and BlockHeight::sub() returns i128.
1903
                //       The subtraction and cast is safe given we passed the above assert.
1904
                let confirmations: BlockHeight =
×
1905
                    ((tip_block_header.height - latest_balance_height) as u64 + 1).into();
×
1906
                Some(confirmations)
×
1907
            }
1908
            None => None,
1✔
1909
        }
1910
    }
1✔
1911

1912
    /// Return temperature of CPU, if available.
1913
    fn cpu_temp_inner() -> Option<f32> {
1✔
1914
        let current_system = System::new();
1✔
1915
        current_system.cpu_temp().ok()
1✔
1916
    }
1✔
1917

1918
    async fn send_to_many_inner_with_mock_proof_option(
21✔
1919
        mut self,
21✔
1920
        outputs: Vec<(ReceivingAddress, NativeCurrencyAmount)>,
21✔
1921
        utxo_notification_media: (UtxoNotificationMedium, UtxoNotificationMedium),
21✔
1922
        fee: NativeCurrencyAmount,
21✔
1923
        now: Timestamp,
21✔
1924
        tx_proving_capability: TxProvingCapability,
21✔
1925
        mocked_invalid_proof: Option<TransactionProof>,
21✔
1926
    ) -> Result<(Transaction, Vec<PrivateNotificationData>), error::SendError> {
21✔
1927
        let (owned_utxo_notification_medium, unowned_utxo_notification_medium) =
21✔
1928
            utxo_notification_media;
21✔
1929

1930
        // check if this send would exceed the send rate-limit (per block)
1931
        {
1932
            // send rate limiting only applies below height 25000
1933
            // which is approx 5.6 months after launch.
1934
            // after that, the training wheel come off.
1935
            const RATE_LIMIT_UNTIL_HEIGHT: u64 = 25000;
1936
            let state = self.state.lock_guard().await;
21✔
1937

1938
            if state.chain.light_state().header().height < RATE_LIMIT_UNTIL_HEIGHT.into() {
21✔
1939
                const RATE_LIMIT: usize = 2;
1940
                let tip_digest = state.chain.light_state().hash();
21✔
1941
                let send_count_at_tip = state
21✔
1942
                    .wallet_state
21✔
1943
                    .count_sent_transactions_at_block(tip_digest)
21✔
1944
                    .await;
21✔
1945
                tracing::debug!(
21✔
1946
                    "send-tx rate-limit check:  found {} sent-tx at current tip.  limit = {}",
×
1947
                    send_count_at_tip,
1948
                    RATE_LIMIT
1949
                );
1950
                if send_count_at_tip >= RATE_LIMIT {
21✔
1951
                    let height = state.chain.light_state().header().height;
8✔
1952
                    let e = error::SendError::RateLimit {
8✔
1953
                        height,
8✔
1954
                        tip_digest,
8✔
1955
                        max: RATE_LIMIT,
8✔
1956
                    };
8✔
1957
                    tracing::warn!("{}", e.to_string());
8✔
1958
                    return Err(e);
8✔
1959
                }
13✔
1960
            }
×
1961
        }
1962

1963
        tracing::debug!("stmi: step 1. get change key. need write-lock");
13✔
1964

1965
        // obtain next unused symmetric key for change utxo
1966
        let change_key = {
13✔
1967
            let mut s = self.state.lock_guard_mut().await;
13✔
1968
            let key = s
13✔
1969
                .wallet_state
13✔
1970
                .next_unused_spending_key(KeyType::Symmetric)
13✔
1971
                .await
13✔
1972
                .expect("wallet should be capable of generating symmetric spending keys");
13✔
1973

13✔
1974
            // write state to disk. create_transaction() may be slow.
13✔
1975
            s.persist_wallet().await.expect("flushed");
13✔
1976
            key
13✔
1977
        };
13✔
1978

13✔
1979
        tracing::debug!("stmi: step 2. generate outputs. need read-lock");
13✔
1980

1981
        let state = self.state.lock_guard().await;
13✔
1982
        let tx_outputs = state.generate_tx_outputs(
13✔
1983
            outputs,
13✔
1984
            owned_utxo_notification_medium,
13✔
1985
            unowned_utxo_notification_medium,
13✔
1986
        );
13✔
1987

13✔
1988
        tracing::debug!("stmi: step 3. create tx. have read-lock");
13✔
1989

1990
        // Create the transaction
1991
        //
1992
        // Note that create_transaction() does not modify any state and only
1993
        // requires acquiring a read-lock which does not block other tasks.
1994
        // This is important because internally it calls prove() which is a very
1995
        // lengthy operation.
1996
        //
1997
        // note: A change output will be added to tx_outputs if needed.
1998
        let (mut transaction, transaction_details, maybe_change_output) = match state
13✔
1999
            .create_transaction_with_prover_capability(
13✔
2000
                tx_outputs.clone(),
13✔
2001
                change_key,
13✔
2002
                owned_utxo_notification_medium,
13✔
2003
                fee,
13✔
2004
                now,
13✔
2005
                tx_proving_capability,
13✔
2006
                self.state.vm_job_queue(),
13✔
2007
            )
13✔
2008
            .await
13✔
2009
        {
2010
            Ok(tx) => tx,
12✔
2011
            Err(e) => {
1✔
2012
                tracing::error!("Could not create transaction: {}", e);
1✔
2013
                return Err(e.into());
1✔
2014
            }
2015
        };
2016
        drop(state);
12✔
2017

2018
        if let Some(invalid_proof) = mocked_invalid_proof {
12✔
2019
            transaction.proof = invalid_proof;
6✔
2020
        }
6✔
2021

2022
        tracing::debug!("stmi: step 4. extract expected utxo. need read-lock");
12✔
2023

2024
        let offchain_notifications = tx_outputs.private_notifications(self.state.cli().network);
12✔
2025
        tracing::debug!(
12✔
2026
            "Generated {} offchain notifications",
×
2027
            offchain_notifications.len()
×
2028
        );
2029

2030
        let utxos_sent_to_self = self
12✔
2031
            .state
12✔
2032
            .lock_guard()
12✔
2033
            .await
12✔
2034
            .wallet_state
2035
            .extract_expected_utxos(
12✔
2036
                tx_outputs.clone().concat_with(maybe_change_output),
12✔
2037
                UtxoNotifier::Myself,
12✔
2038
            );
12✔
2039

12✔
2040
        // if the tx created offchain expected_utxos we must inform wallet.
12✔
2041
        if !utxos_sent_to_self.is_empty() {
12✔
2042
            tracing::debug!("stmi: step 5. add expected utxos. need write-lock");
9✔
2043

2044
            // acquire write-lock
2045
            let mut gsm = self.state.lock_guard_mut().await;
9✔
2046

2047
            // Inform wallet of any expected incoming utxos.
2048
            // note that this (briefly) mutates self.
2049
            gsm.wallet_state
9✔
2050
                .add_expected_utxos(utxos_sent_to_self)
9✔
2051
                .await;
9✔
2052

2053
            // ensure we write new wallet state out to disk.
2054
            gsm.persist_wallet().await.expect("flushed wallet");
9✔
2055
        }
3✔
2056

2057
        // write-lock block.
2058
        {
2059
            tracing::debug!("stmi: step 6. add sent-transaction to wallet. with write-lock");
12✔
2060

2061
            let mut gsm = self.state.lock_guard_mut().await;
12✔
2062
            // inform wallet about the details of this sent transaction, so it can
2063
            // group inputs and outputs together, eg for history purposes.
2064
            let tip_digest = gsm.chain.light_state().hash();
12✔
2065
            gsm.wallet_state
12✔
2066
                .add_sent_transaction((transaction_details, tip_digest).into())
12✔
2067
                .await;
12✔
2068

2069
            tracing::debug!("stmi: step 7. flush dbs.  with write-lock");
12✔
2070
            gsm.flush_databases().await.expect("flushed DBs");
12✔
2071
        }
12✔
2072

12✔
2073
        tracing::debug!("stmi: step 8. send messages. no lock needed");
12✔
2074

2075
        // Send transaction message to main
2076
        let response = self
12✔
2077
            .rpc_server_to_main_tx
12✔
2078
            .send(RPCServerToMain::BroadcastTx(Box::new(transaction.clone())))
12✔
2079
            .await;
12✔
2080

2081
        if let Err(e) = response {
12✔
2082
            tracing::error!("Could not send Tx to main task: error: {}", e.to_string());
×
2083
            return Err(error::SendError::NotBroadcast);
×
2084
        };
12✔
2085

12✔
2086
        tracing::debug!("stmi: step 8. all done with send_to_many_inner().");
12✔
2087

2088
        Ok((transaction, offchain_notifications))
12✔
2089
    }
21✔
2090

2091
    /// Method to create a transaction with a given timestamp and prover
2092
    /// capability.
2093
    ///
2094
    /// Factored out from [NeptuneRPCServer::send_to_many] in order to generate
2095
    /// deterministic transaction kernels where tests can reuse previously
2096
    /// generated proofs.
2097
    ///
2098
    /// Locking:
2099
    ///   * acquires `global_state_lock` for write
2100
    async fn send_to_many_inner(
15✔
2101
        self,
15✔
2102
        _ctx: context::Context,
15✔
2103
        outputs: Vec<(ReceivingAddress, NativeCurrencyAmount)>,
15✔
2104
        utxo_notification_media: (UtxoNotificationMedium, UtxoNotificationMedium),
15✔
2105
        fee: NativeCurrencyAmount,
15✔
2106
        now: Timestamp,
15✔
2107
        tx_proving_capability: TxProvingCapability,
15✔
2108
    ) -> Result<(TransactionKernelId, Vec<PrivateNotificationData>), error::SendError> {
15✔
2109
        let (owned_utxo_notification_medium, unowned_utxo_notification_medium) =
15✔
2110
            utxo_notification_media;
15✔
2111
        let ret = self
15✔
2112
            .send_to_many_inner_with_mock_proof_option(
15✔
2113
                outputs,
15✔
2114
                (
15✔
2115
                    owned_utxo_notification_medium,
15✔
2116
                    unowned_utxo_notification_medium,
15✔
2117
                ),
15✔
2118
                fee,
15✔
2119
                now,
15✔
2120
                tx_proving_capability,
15✔
2121
                None,
15✔
2122
            )
15✔
2123
            .await;
15✔
2124

2125
        ret.map(|(tx, offchain_notifications)| (tx.kernel.txid(), offchain_notifications))
15✔
2126
    }
15✔
2127

2128
    /// Like [Self::send_to_many_inner] but without attempting to create a valid
2129
    /// SingleProof, since this is a time-consuming process.
2130
    ///
2131
    /// Also returns the full transaction and not just its kernel ID.
2132
    #[cfg(test)]
2133
    async fn send_to_many_inner_invalid_proof(
6✔
2134
        self,
6✔
2135
        outputs: Vec<(ReceivingAddress, NativeCurrencyAmount)>,
6✔
2136
        owned_utxo_notification_medium: UtxoNotificationMedium,
6✔
2137
        unowned_utxo_notification_medium: UtxoNotificationMedium,
6✔
2138
        fee: NativeCurrencyAmount,
6✔
2139
        now: Timestamp,
6✔
2140
    ) -> Option<(Transaction, Vec<PrivateNotificationData>)> {
6✔
2141
        self.send_to_many_inner_with_mock_proof_option(
6✔
2142
            outputs,
6✔
2143
            (
6✔
2144
                owned_utxo_notification_medium,
6✔
2145
                unowned_utxo_notification_medium,
6✔
2146
            ),
6✔
2147
            fee,
6✔
2148
            now,
6✔
2149
            TxProvingCapability::PrimitiveWitness,
6✔
2150
            Some(TransactionProof::invalid()),
6✔
2151
        )
6✔
2152
        .await
6✔
2153
        .ok()
6✔
2154
    }
6✔
2155

2156
    /// Assemble a data for the wallet to register the UTXO. Returns `Ok(None)`
2157
    /// if the UTXO has already been claimed by the wallet.
2158
    ///
2159
    /// `max_search_depth` denotes how many blocks back from tip we attempt
2160
    /// to find the transaction in a block. `None` means unlimited.
2161
    ///
2162
    /// `encrypted_utxo_notification` is expected to hold encrypted data about
2163
    /// a future or past UTXO, which can be claimed by this client.
2164
    async fn claim_utxo_inner(
14✔
2165
        &self,
14✔
2166
        encrypted_utxo_notification: String,
14✔
2167
        max_search_depth: Option<u64>,
14✔
2168
    ) -> Result<Option<ClaimUtxoData>, error::ClaimError> {
14✔
2169
        let span = tracing::debug_span!("Claim UTXO inner");
14✔
2170
        let _enter = span.enter();
14✔
2171

2172
        // deserialize UtxoTransferEncrypted from bech32m string.
2173
        let utxo_transfer_encrypted = EncryptedUtxoNotification::from_bech32m(
14✔
2174
            &encrypted_utxo_notification,
14✔
2175
            self.state.cli().network,
14✔
2176
        )?;
14✔
2177

2178
        // // acquire global state read lock
2179
        let state = self.state.lock_guard().await;
14✔
2180

2181
        // find known spending key by receiver_identifier
2182
        let spending_key = state
14✔
2183
            .wallet_state
14✔
2184
            .find_known_spending_key_for_receiver_identifier(
14✔
2185
                utxo_transfer_encrypted.receiver_identifier,
14✔
2186
            )
14✔
2187
            .ok_or(error::ClaimError::UtxoUnknown)?;
14✔
2188

2189
        // decrypt utxo_transfer_encrypted into UtxoTransfer
2190
        let utxo_notification = utxo_transfer_encrypted
14✔
2191
            .decrypt_with_spending_key(&spending_key)
14✔
2192
            .expect(
14✔
2193
                "spending key should be capable of decryption because it was returned by find_known_spending_key_for_receiver_identifier",
14✔
2194
            )?;
14✔
2195

2196
        tracing::debug!("claim-utxo: decrypted {:#?}", utxo_notification);
14✔
2197

2198
        // search for matching monitored utxo and return early if found.
2199
        if state
14✔
2200
            .wallet_state
14✔
2201
            .find_monitored_utxo(&utxo_notification.utxo, utxo_notification.sender_randomness)
14✔
2202
            .await
14✔
2203
            .is_some()
14✔
2204
        {
2205
            info!("found monitored utxo. Returning early.");
6✔
2206
            return Ok(None);
6✔
2207
        }
8✔
2208

8✔
2209
        // construct an IncomingUtxo
8✔
2210
        let incoming_utxo = IncomingUtxo::from_utxo_notification_payload(utxo_notification, spending_key.privacy_preimage().expect("spending key should have associated address and privacy preimage because it was returned by find_known_spending_key_for_receiver_identifier"));
8✔
2211

8✔
2212
        // Check if we can satisfy typescripts
8✔
2213
        if !incoming_utxo.utxo.all_type_script_states_are_valid() {
8✔
2214
            let err = error::ClaimError::InvalidTypeScript;
×
2215
            warn!("{}", err.to_string());
×
2216
            return Err(err);
×
2217
        }
8✔
2218

8✔
2219
        // check if wallet is already expecting this utxo.
8✔
2220
        let addition_record = incoming_utxo.addition_record();
8✔
2221
        let has_expected_utxo = state.wallet_state.has_expected_utxo(addition_record).await;
8✔
2222

2223
        // Check if UTXO has already been mined in a transaction.
2224
        let mined_in_block = state
8✔
2225
            .chain
8✔
2226
            .archival_state()
8✔
2227
            .find_canonical_block_with_output(addition_record, max_search_depth)
8✔
2228
            .await;
8✔
2229
        let maybe_prepared_mutxo = match mined_in_block {
8✔
2230
            Some(block) => {
2✔
2231
                let aocl_leaf_index = {
2✔
2232
                    // Find matching AOCL leaf index that must be in this block
2233
                    let last_aocl_index_in_block =
2✔
2234
                        block.mutator_set_accumulator_after().aocl.num_leafs() - 1;
2✔
2235
                    let num_outputs_in_block: u64 = block
2✔
2236
                        .mutator_set_update()
2✔
2237
                        .additions
2✔
2238
                        .len()
2✔
2239
                        .try_into()
2✔
2240
                        .unwrap();
2✔
2241
                    let min_aocl_leaf_index = last_aocl_index_in_block - num_outputs_in_block + 1;
2✔
2242
                    let mut haystack = last_aocl_index_in_block;
2✔
2243
                    let ams = state.chain.archival_state().archival_mutator_set.ams();
2✔
2244
                    while ams.aocl.get_leaf_async(haystack).await
9✔
2245
                        != addition_record.canonical_commitment
9✔
2246
                    {
2247
                        assert!(haystack > min_aocl_leaf_index);
7✔
2248
                        haystack -= 1;
7✔
2249
                    }
2250

2251
                    haystack
2✔
2252
                };
2✔
2253
                let item = Tip5::hash(&incoming_utxo.utxo);
2✔
2254
                let ams = state.chain.archival_state().archival_mutator_set.ams();
2✔
2255
                let msmp = ams
2✔
2256
                    .restore_membership_proof(
2✔
2257
                        item,
2✔
2258
                        incoming_utxo.sender_randomness,
2✔
2259
                        incoming_utxo.receiver_preimage,
2✔
2260
                        aocl_leaf_index,
2✔
2261
                    )
2✔
2262
                    .await
2✔
2263
                    .map_err(|x| anyhow!("Could not restore mutator set membership proof. Is archival mutator set corrupted? Got error: {x}"))?;
2✔
2264

2265
                let tip_digest = state.chain.light_state().hash();
2✔
2266

2✔
2267
                let mut monitored_utxo = MonitoredUtxo::new(
2✔
2268
                    incoming_utxo.utxo.clone(),
2✔
2269
                    self.state.cli().number_of_mps_per_utxo,
2✔
2270
                );
2✔
2271
                monitored_utxo.confirmed_in_block = Some((
2✔
2272
                    block.hash(),
2✔
2273
                    block.header().timestamp,
2✔
2274
                    block.header().height,
2✔
2275
                ));
2✔
2276
                monitored_utxo.add_membership_proof_for_tip(tip_digest, msmp.clone());
2✔
2277

2278
                // Was UTXO already spent? If so, register it as such.
2279
                let msa = ams.accumulator().await;
2✔
2280
                if !msa.verify(item, &msmp) {
2✔
2281
                    warn!("Claimed UTXO was already spent. Marking it as such.");
×
2282

2283
                    if let Some(spending_block) = state
×
2284
                        .chain
×
2285
                        .archival_state()
×
2286
                        .find_canonical_block_with_input(
×
2287
                            msmp.compute_indices(item),
×
2288
                            max_search_depth,
×
2289
                        )
×
2290
                        .await
×
2291
                    {
2292
                        warn!(
×
2293
                            "Claimed UTXO was spent in block {}; which has height {}",
×
2294
                            spending_block.hash(),
×
2295
                            spending_block.header().height
×
2296
                        );
2297
                        monitored_utxo.mark_as_spent(&spending_block);
×
2298
                    } else {
2299
                        error!("Claimed UTXO's mutator set membership proof was invalid but we could not find the block in which it was spent. This is most likely a bug in the software.");
×
2300
                    }
2301
                }
2✔
2302

2303
                Some(monitored_utxo)
2✔
2304
            }
2305
            None => None,
6✔
2306
        };
2307

2308
        let expected_utxo = incoming_utxo.into_expected_utxo(UtxoNotifier::Cli);
8✔
2309
        Ok(Some(ClaimUtxoData {
8✔
2310
            prepared_monitored_utxo: maybe_prepared_mutxo,
8✔
2311
            has_expected_utxo,
8✔
2312
            expected_utxo,
8✔
2313
        }))
8✔
2314
    }
14✔
2315

2316
    /// Return a PoW puzzle with the provided guesser digest.
2317
    async fn pow_puzzle_inner(
14✔
2318
        mut self,
14✔
2319
        guesser_key_after_image: Digest,
14✔
2320
        mut proposal: Block,
14✔
2321
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
14✔
2322
        let latest_block_header = *self.state.lock_guard().await.chain.light_state().header();
14✔
2323

14✔
2324
        proposal.set_header_guesser_digest(guesser_key_after_image);
14✔
2325
        let puzzle = ProofOfWorkPuzzle::new(proposal.clone(), latest_block_header);
14✔
2326

2327
        // Record block proposal in case of guesser-success, for later
2328
        // retrieval. But limit number of blocks stored this way.
2329
        let mut state = self.state.lock_guard_mut().await;
14✔
2330
        if state.mining_state.exported_block_proposals.len()
14✔
2331
            >= MAX_NUM_EXPORTED_BLOCK_PROPOSAL_STORED
14✔
2332
        {
2333
            return Err(error::RpcError::ExportedBlockProposalStorageCapacityExceeded);
×
2334
        }
14✔
2335

14✔
2336
        state
14✔
2337
            .mining_state
14✔
2338
            .exported_block_proposals
14✔
2339
            .insert(puzzle.id, proposal);
14✔
2340

14✔
2341
        Ok(Some(puzzle))
14✔
2342
    }
14✔
2343

2344
    /// get the data_directory for this neptune-core instance
2345
    pub fn data_directory(&self) -> &DataDirectory {
19✔
2346
        &self.data_directory
19✔
2347
    }
19✔
2348
}
2349

2350
impl RPC for NeptuneRPCServer {
2351
    // documented in trait. do not add doc-comment.
2352
    async fn cookie_hint(self, _: context::Context) -> RpcResult<rpc_auth::CookieHint> {
×
2353
        log_slow_scope!(fn_name!());
×
2354

×
2355
        if self.state.cli().disable_cookie_hint {
×
2356
            Err(error::RpcError::CookieHintDisabled)
×
2357
        } else {
2358
            Ok(rpc_auth::CookieHint {
×
2359
                data_directory: self.data_directory().to_owned(),
×
2360
                network: self.state.cli().network,
×
2361
            })
×
2362
        }
2363
    }
×
2364

2365
    // documented in trait. do not add doc-comment.
2366
    async fn network(self, _: context::Context) -> RpcResult<Network> {
6✔
2367
        log_slow_scope!(fn_name!());
6✔
2368

6✔
2369
        Ok(self.state.cli().network)
6✔
2370
    }
6✔
2371

2372
    // documented in trait. do not add doc-comment.
2373
    async fn own_listen_address_for_peers(
1✔
2374
        self,
1✔
2375
        _context: context::Context,
1✔
2376
        token: rpc_auth::Token,
1✔
2377
    ) -> RpcResult<Option<SocketAddr>> {
1✔
2378
        log_slow_scope!(fn_name!());
1✔
2379
        token.auth(&self.valid_tokens)?;
1✔
2380

2381
        let listen_port = self.state.cli().own_listen_port();
1✔
2382
        let listen_for_peers_ip = self.state.cli().listen_addr;
1✔
2383
        Ok(listen_port.map(|port| SocketAddr::new(listen_for_peers_ip, port)))
1✔
2384
    }
1✔
2385

2386
    // documented in trait. do not add doc-comment.
2387
    async fn own_instance_id(
1✔
2388
        self,
1✔
2389
        _context: context::Context,
1✔
2390
        token: rpc_auth::Token,
1✔
2391
    ) -> RpcResult<InstanceId> {
1✔
2392
        log_slow_scope!(fn_name!());
1✔
2393
        token.auth(&self.valid_tokens)?;
1✔
2394

2395
        Ok(self.state.lock_guard().await.net.instance_id)
1✔
2396
    }
1✔
2397

2398
    // documented in trait. do not add doc-comment.
2399
    async fn block_height(
1✔
2400
        self,
1✔
2401
        _: context::Context,
1✔
2402
        token: rpc_auth::Token,
1✔
2403
    ) -> RpcResult<BlockHeight> {
1✔
2404
        log_slow_scope!(fn_name!());
1✔
2405
        token.auth(&self.valid_tokens)?;
1✔
2406

2407
        Ok(self
1✔
2408
            .state
1✔
2409
            .lock_guard()
1✔
2410
            .await
1✔
2411
            .chain
2412
            .light_state()
1✔
2413
            .kernel
2414
            .header
2415
            .height)
2416
    }
1✔
2417

2418
    // documented in trait. do not add doc-comment.
2419
    async fn confirmations(
×
2420
        self,
×
2421
        _: context::Context,
×
2422
        token: rpc_auth::Token,
×
2423
    ) -> RpcResult<Option<BlockHeight>> {
×
2424
        log_slow_scope!(fn_name!());
×
2425
        token.auth(&self.valid_tokens)?;
×
2426

2427
        let guard = self.state.lock_guard().await;
×
2428
        Ok(self.confirmations_internal(&guard).await)
×
2429
    }
×
2430

2431
    // documented in trait. do not add doc-comment.
2432
    async fn utxo_digest(
3✔
2433
        self,
3✔
2434
        _: context::Context,
3✔
2435
        token: rpc_auth::Token,
3✔
2436
        leaf_index: u64,
3✔
2437
    ) -> RpcResult<Option<Digest>> {
3✔
2438
        log_slow_scope!(fn_name!());
3✔
2439
        token.auth(&self.valid_tokens)?;
3✔
2440

2441
        let state = self.state.lock_guard().await;
3✔
2442
        let aocl = &state.chain.archival_state().archival_mutator_set.ams().aocl;
3✔
2443

3✔
2444
        Ok(
3✔
2445
            match leaf_index > 0 && leaf_index < aocl.num_leafs().await {
3✔
2446
                true => Some(aocl.get_leaf_async(leaf_index).await),
1✔
2447
                false => None,
2✔
2448
            },
2449
        )
2450
    }
3✔
2451

2452
    // documented in trait. do not add doc-comment.
2453
    async fn block_digest(
7✔
2454
        self,
7✔
2455
        _: context::Context,
7✔
2456
        token: rpc_auth::Token,
7✔
2457
        block_selector: BlockSelector,
7✔
2458
    ) -> RpcResult<Option<Digest>> {
7✔
2459
        log_slow_scope!(fn_name!());
7✔
2460
        token.auth(&self.valid_tokens)?;
7✔
2461

2462
        let state = self.state.lock_guard().await;
7✔
2463
        let archival_state = state.chain.archival_state();
7✔
2464
        let Some(digest) = block_selector.as_digest(&state).await else {
7✔
2465
            return Ok(None);
1✔
2466
        };
2467
        // verify the block actually exists
2468
        Ok(archival_state
6✔
2469
            .get_block_header(digest)
6✔
2470
            .await
6✔
2471
            .map(|_| digest))
6✔
2472
    }
7✔
2473

2474
    // documented in trait. do not add doc-comment.
2475
    async fn block_info(
7✔
2476
        self,
7✔
2477
        _: context::Context,
7✔
2478
        token: rpc_auth::Token,
7✔
2479
        block_selector: BlockSelector,
7✔
2480
    ) -> RpcResult<Option<BlockInfo>> {
7✔
2481
        log_slow_scope!(fn_name!());
7✔
2482
        token.auth(&self.valid_tokens)?;
7✔
2483

2484
        let state = self.state.lock_guard().await;
7✔
2485
        let Some(digest) = block_selector.as_digest(&state).await else {
7✔
2486
            return Ok(None);
1✔
2487
        };
2488
        let tip_digest = state.chain.light_state().hash();
6✔
2489
        let archival_state = state.chain.archival_state();
6✔
2490

2491
        let Some(block) = archival_state.get_block(digest).await.unwrap() else {
6✔
2492
            return Ok(None);
2✔
2493
        };
2494
        let is_canonical = archival_state
4✔
2495
            .block_belongs_to_canonical_chain(digest)
4✔
2496
            .await;
4✔
2497

2498
        // sibling blocks are those at the same height, with different digest
2499
        let sibling_blocks = archival_state
4✔
2500
            .block_height_to_block_digests(block.header().height)
4✔
2501
            .await
4✔
2502
            .into_iter()
4✔
2503
            .filter(|d| *d != digest)
4✔
2504
            .collect();
4✔
2505

4✔
2506
        Ok(Some(BlockInfo::new(
4✔
2507
            &block,
4✔
2508
            archival_state.genesis_block().hash(),
4✔
2509
            tip_digest,
4✔
2510
            sibling_blocks,
4✔
2511
            is_canonical,
4✔
2512
        )))
4✔
2513
    }
7✔
2514

2515
    // documented in trait. do not add doc-comment.
2516
    async fn public_announcements_in_block(
4✔
2517
        self,
4✔
2518
        _context: tarpc::context::Context,
4✔
2519
        token: rpc_auth::Token,
4✔
2520
        block_selector: BlockSelector,
4✔
2521
    ) -> RpcResult<Option<Vec<PublicAnnouncement>>> {
4✔
2522
        log_slow_scope!(fn_name!());
4✔
2523
        token.auth(&self.valid_tokens)?;
4✔
2524

2525
        let state = self.state.lock_guard().await;
4✔
2526
        let Some(digest) = block_selector.as_digest(&state).await else {
4✔
2527
            return Ok(None);
1✔
2528
        };
2529
        let archival_state = state.chain.archival_state();
3✔
2530
        let Some(block) = archival_state.get_block(digest).await.unwrap() else {
3✔
2531
            return Ok(None);
1✔
2532
        };
2533

2534
        Ok(Some(
2✔
2535
            block.body().transaction_kernel.public_announcements.clone(),
2✔
2536
        ))
2✔
2537
    }
4✔
2538

2539
    // documented in trait. do not add doc-comment.
2540
    async fn block_digests_by_height(
2✔
2541
        self,
2✔
2542
        _: context::Context,
2✔
2543
        token: rpc_auth::Token,
2✔
2544
        height: BlockHeight,
2✔
2545
    ) -> RpcResult<Vec<Digest>> {
2✔
2546
        log_slow_scope!(fn_name!());
2✔
2547
        token.auth(&self.valid_tokens)?;
2✔
2548

2549
        Ok(self
2✔
2550
            .state
2✔
2551
            .lock_guard()
2✔
2552
            .await
2✔
2553
            .chain
2554
            .archival_state()
2✔
2555
            .block_height_to_block_digests(height)
2✔
2556
            .await)
2✔
2557
    }
2✔
2558

2559
    // documented in trait. do not add doc-comment.
2560
    async fn latest_tip_digests(
1✔
2561
        self,
1✔
2562
        _context: tarpc::context::Context,
1✔
2563
        token: rpc_auth::Token,
1✔
2564
        n: usize,
1✔
2565
    ) -> RpcResult<Vec<Digest>> {
1✔
2566
        log_slow_scope!(fn_name!());
1✔
2567
        token.auth(&self.valid_tokens)?;
1✔
2568

2569
        let state = self.state.lock_guard().await;
1✔
2570

2571
        let latest_block_digest = state.chain.light_state().hash();
1✔
2572

1✔
2573
        Ok(state
1✔
2574
            .chain
1✔
2575
            .archival_state()
1✔
2576
            .get_ancestor_block_digests(latest_block_digest, n)
1✔
2577
            .await)
1✔
2578
    }
1✔
2579

2580
    // documented in trait. do not add doc-comment.
2581
    async fn peer_info(
1✔
2582
        self,
1✔
2583
        _: context::Context,
1✔
2584
        token: rpc_auth::Token,
1✔
2585
    ) -> RpcResult<Vec<PeerInfo>> {
1✔
2586
        log_slow_scope!(fn_name!());
1✔
2587
        token.auth(&self.valid_tokens)?;
1✔
2588

2589
        Ok(self
1✔
2590
            .state
1✔
2591
            .lock_guard()
1✔
2592
            .await
1✔
2593
            .net
2594
            .peer_map
2595
            .values()
1✔
2596
            .cloned()
1✔
2597
            .collect())
1✔
2598
    }
1✔
2599

2600
    // documented in trait. do not add doc-comment.
2601
    async fn all_punished_peers(
7✔
2602
        self,
7✔
2603
        _context: tarpc::context::Context,
7✔
2604
        token: rpc_auth::Token,
7✔
2605
    ) -> RpcResult<HashMap<IpAddr, PeerStanding>> {
7✔
2606
        log_slow_scope!(fn_name!());
7✔
2607
        token.auth(&self.valid_tokens)?;
7✔
2608

2609
        let mut sanctions_in_memory = HashMap::default();
7✔
2610

2611
        let global_state = self.state.lock_guard().await;
7✔
2612

2613
        // Get all connected peers
2614
        for (socket_address, peer_info) in &global_state.net.peer_map {
14✔
2615
            if peer_info.standing().is_negative() {
14✔
2616
                sanctions_in_memory.insert(socket_address.ip(), peer_info.standing());
7✔
2617
            }
7✔
2618
        }
2619

2620
        let sanctions_in_db = global_state.net.all_peer_sanctions_in_database();
7✔
2621

7✔
2622
        // Combine result for currently connected peers and previously connected peers but
7✔
2623
        // use result for currently connected peer if there is an overlap
7✔
2624
        let mut all_sanctions = sanctions_in_memory;
7✔
2625
        for (ip_addr, sanction) in sanctions_in_db {
12✔
2626
            if sanction.is_negative() {
5✔
2627
                all_sanctions.entry(ip_addr).or_insert(sanction);
5✔
2628
            }
5✔
2629
        }
2630

2631
        Ok(all_sanctions)
7✔
2632
    }
7✔
2633

2634
    // documented in trait. do not add doc-comment.
2635
    async fn validate_address(
1✔
2636
        self,
1✔
2637
        _ctx: context::Context,
1✔
2638
        token: rpc_auth::Token,
1✔
2639
        address_string: String,
1✔
2640
        network: Network,
1✔
2641
    ) -> RpcResult<Option<ReceivingAddress>> {
1✔
2642
        log_slow_scope!(fn_name!());
1✔
2643
        token.auth(&self.valid_tokens)?;
1✔
2644

2645
        let ret = ReceivingAddress::from_bech32m(&address_string, network).ok();
1✔
2646
        tracing::debug!(
1✔
2647
            "Responding to address validation request of {address_string}: {}",
×
2648
            ret.is_some()
×
2649
        );
2650
        Ok(ret)
1✔
2651
    }
1✔
2652

2653
    // documented in trait. do not add doc-comment.
2654
    async fn validate_amount(
×
2655
        self,
×
2656
        _ctx: context::Context,
×
2657
        token: rpc_auth::Token,
×
2658
        amount_string: String,
×
2659
    ) -> RpcResult<Option<NativeCurrencyAmount>> {
×
2660
        log_slow_scope!(fn_name!());
×
2661
        token.auth(&self.valid_tokens)?;
×
2662

2663
        // parse string
2664
        if let Ok(amt) = NativeCurrencyAmount::coins_from_str(&amount_string) {
×
2665
            Ok(Some(amt))
×
2666
        } else {
2667
            Ok(None)
×
2668
        }
2669
    }
×
2670

2671
    // documented in trait. do not add doc-comment.
2672
    async fn amount_leq_confirmed_available_balance(
×
2673
        self,
×
2674
        _ctx: context::Context,
×
2675
        token: rpc_auth::Token,
×
2676
        amount: NativeCurrencyAmount,
×
2677
    ) -> RpcResult<bool> {
×
2678
        log_slow_scope!(fn_name!());
×
2679
        token.auth(&self.valid_tokens)?;
×
2680

2681
        let gs = self.state.lock_guard().await;
×
2682
        let wallet_status = gs.get_wallet_status_for_tip().await;
×
2683

2684
        let confirmed_available = gs
×
2685
            .wallet_state
×
2686
            .confirmed_available_balance(&wallet_status, Timestamp::now());
×
2687

×
2688
        // test inequality
×
2689
        Ok(amount <= confirmed_available)
×
2690
    }
×
2691

2692
    // documented in trait. do not add doc-comment.
2693
    async fn confirmed_available_balance(
9✔
2694
        self,
9✔
2695
        _context: tarpc::context::Context,
9✔
2696
        token: rpc_auth::Token,
9✔
2697
    ) -> RpcResult<NativeCurrencyAmount> {
9✔
2698
        log_slow_scope!(fn_name!());
9✔
2699
        token.auth(&self.valid_tokens)?;
9✔
2700

2701
        let gs = self.state.lock_guard().await;
9✔
2702
        let wallet_status = gs.get_wallet_status_for_tip().await;
9✔
2703

2704
        let confirmed_available = gs
9✔
2705
            .wallet_state
9✔
2706
            .confirmed_available_balance(&wallet_status, Timestamp::now());
9✔
2707

9✔
2708
        Ok(confirmed_available)
9✔
2709
    }
9✔
2710

2711
    // documented in trait. do not add doc-comment.
2712
    async fn unconfirmed_available_balance(
×
2713
        self,
×
2714
        _context: tarpc::context::Context,
×
2715
        token: rpc_auth::Token,
×
2716
    ) -> RpcResult<NativeCurrencyAmount> {
×
2717
        log_slow_scope!(fn_name!());
×
2718
        token.auth(&self.valid_tokens)?;
×
2719

2720
        let gs = self.state.lock_guard().await;
×
2721
        let wallet_status = gs.get_wallet_status_for_tip().await;
×
2722

2723
        Ok(gs
×
2724
            .wallet_state
×
2725
            .unconfirmed_available_balance(&wallet_status, Timestamp::now()))
×
2726
    }
×
2727

2728
    // documented in trait. do not add doc-comment.
2729
    async fn wallet_status(
1✔
2730
        self,
1✔
2731
        _context: tarpc::context::Context,
1✔
2732
        token: rpc_auth::Token,
1✔
2733
    ) -> RpcResult<WalletStatus> {
1✔
2734
        log_slow_scope!(fn_name!());
1✔
2735
        token.auth(&self.valid_tokens)?;
1✔
2736

2737
        Ok(self
1✔
2738
            .state
1✔
2739
            .lock_guard()
1✔
2740
            .await
1✔
2741
            .get_wallet_status_for_tip()
1✔
2742
            .await)
1✔
2743
    }
1✔
2744

2745
    async fn num_expected_utxos(
×
2746
        self,
×
2747
        _context: tarpc::context::Context,
×
2748
        token: rpc_auth::Token,
×
2749
    ) -> RpcResult<u64> {
×
2750
        log_slow_scope!(fn_name!());
×
2751
        token.auth(&self.valid_tokens)?;
×
2752

2753
        Ok(self
×
2754
            .state
×
2755
            .lock_guard()
×
2756
            .await
×
2757
            .wallet_state
2758
            .num_expected_utxos()
×
2759
            .await)
×
2760
    }
×
2761

2762
    // documented in trait. do not add doc-comment.
2763
    async fn header(
1✔
2764
        self,
1✔
2765
        _context: tarpc::context::Context,
1✔
2766
        token: rpc_auth::Token,
1✔
2767
        block_selector: BlockSelector,
1✔
2768
    ) -> RpcResult<Option<BlockHeader>> {
1✔
2769
        log_slow_scope!(fn_name!());
1✔
2770
        token.auth(&self.valid_tokens)?;
1✔
2771

2772
        let state = self.state.lock_guard().await;
1✔
2773
        let Some(block_digest) = block_selector.as_digest(&state).await else {
1✔
2774
            return Ok(None);
×
2775
        };
2776
        Ok(state
1✔
2777
            .chain
1✔
2778
            .archival_state()
1✔
2779
            .get_block_header(block_digest)
1✔
2780
            .await)
1✔
2781
    }
1✔
2782

2783
    // documented in trait. do not add doc-comment.
2784
    async fn next_receiving_address(
12✔
2785
        mut self,
12✔
2786
        _context: tarpc::context::Context,
12✔
2787
        token: rpc_auth::Token,
12✔
2788
        key_type: KeyType,
12✔
2789
    ) -> RpcResult<Option<ReceivingAddress>> {
12✔
2790
        log_slow_scope!(fn_name!());
12✔
2791
        token.auth(&self.valid_tokens)?;
12✔
2792

2793
        let mut global_state_mut = self.state.lock_guard_mut().await;
12✔
2794

2795
        let address = global_state_mut
12✔
2796
            .wallet_state
12✔
2797
            .next_unused_spending_key(key_type)
12✔
2798
            .await
12✔
2799
            .and_then(|spending_key| spending_key.to_address());
12✔
2800

12✔
2801
        // persist wallet state to disk
12✔
2802
        global_state_mut.persist_wallet().await.expect("flushed");
12✔
2803

12✔
2804
        Ok(address)
12✔
2805
    }
12✔
2806

2807
    // documented in trait. do not add doc-comment.
2808
    async fn known_keys(
×
2809
        self,
×
2810
        _context: tarpc::context::Context,
×
2811
        token: rpc_auth::Token,
×
2812
    ) -> RpcResult<Vec<SpendingKey>> {
×
2813
        log_slow_scope!(fn_name!());
×
2814
        token.auth(&self.valid_tokens)?;
×
2815

2816
        Ok(self
×
2817
            .state
×
2818
            .lock_guard()
×
2819
            .await
×
2820
            .wallet_state
2821
            .get_all_known_spending_keys()
×
2822
            .collect())
×
2823
    }
×
2824

2825
    // documented in trait. do not add doc-comment.
2826
    async fn known_keys_by_keytype(
×
2827
        self,
×
2828
        _context: tarpc::context::Context,
×
2829
        token: rpc_auth::Token,
×
2830
        key_type: KeyType,
×
2831
    ) -> RpcResult<Vec<SpendingKey>> {
×
2832
        log_slow_scope!(fn_name!());
×
2833
        token.auth(&self.valid_tokens)?;
×
2834

2835
        Ok(self
×
2836
            .state
×
2837
            .lock_guard()
×
2838
            .await
×
2839
            .wallet_state
2840
            .get_known_spending_keys(key_type)
×
2841
            .collect())
×
2842
    }
×
2843

2844
    // documented in trait. do not add doc-comment.
2845
    async fn mempool_tx_count(
1✔
2846
        self,
1✔
2847
        _context: tarpc::context::Context,
1✔
2848
        token: rpc_auth::Token,
1✔
2849
    ) -> RpcResult<usize> {
1✔
2850
        log_slow_scope!(fn_name!());
1✔
2851
        token.auth(&self.valid_tokens)?;
1✔
2852

2853
        Ok(self.state.lock_guard().await.mempool.len())
1✔
2854
    }
1✔
2855

2856
    // documented in trait. do not add doc-comment.
2857
    async fn mempool_size(
1✔
2858
        self,
1✔
2859
        _context: tarpc::context::Context,
1✔
2860
        token: rpc_auth::Token,
1✔
2861
    ) -> RpcResult<usize> {
1✔
2862
        log_slow_scope!(fn_name!());
1✔
2863
        token.auth(&self.valid_tokens)?;
1✔
2864

2865
        Ok(self.state.lock_guard().await.mempool.get_size())
1✔
2866
    }
1✔
2867

2868
    // documented in trait. do not add doc-comment.
2869
    async fn history(
1✔
2870
        self,
1✔
2871
        _context: tarpc::context::Context,
1✔
2872
        token: rpc_auth::Token,
1✔
2873
    ) -> RpcResult<Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)>> {
1✔
2874
        log_slow_scope!(fn_name!());
1✔
2875
        token.auth(&self.valid_tokens)?;
1✔
2876

2877
        let history = self.state.lock_guard().await.get_balance_history().await;
1✔
2878

2879
        // sort
2880
        let mut display_history: Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)> =
1✔
2881
            history
1✔
2882
                .iter()
1✔
2883
                .map(|(h, t, bh, a)| (*h, *bh, *t, *a))
1✔
2884
                .collect::<Vec<_>>();
1✔
2885
        display_history.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
1✔
2886

1✔
2887
        // return
1✔
2888
        Ok(display_history)
1✔
2889
    }
1✔
2890

2891
    // documented in trait. do not add doc-comment.
2892
    async fn dashboard_overview_data(
1✔
2893
        self,
1✔
2894
        _context: tarpc::context::Context,
1✔
2895
        token: rpc_auth::Token,
1✔
2896
    ) -> RpcResult<DashBoardOverviewDataFromClient> {
1✔
2897
        log_slow_scope!(fn_name!());
1✔
2898
        token.auth(&self.valid_tokens)?;
1✔
2899

2900
        let now = Timestamp::now();
1✔
2901
        let state = self.state.lock_guard().await;
1✔
2902
        let tip_digest = {
1✔
2903
            log_slow_scope!(fn_name!() + "::hash() tip digest");
1✔
2904
            state.chain.light_state().hash()
1✔
2905
        };
1✔
2906
        let tip_header = *state.chain.light_state().header();
1✔
2907
        let syncing = state.net.sync_anchor.is_some();
1✔
2908
        let mempool_size = {
1✔
2909
            log_slow_scope!(fn_name!() + "::mempool.get_size()");
1✔
2910
            state.mempool.get_size()
1✔
2911
        };
1✔
2912
        let mempool_total_tx_count = {
1✔
2913
            log_slow_scope!(fn_name!() + "::mempool.len()");
1✔
2914
            state.mempool.len()
1✔
2915
        };
1✔
2916
        let mempool_own_tx_count = {
1✔
2917
            log_slow_scope!(fn_name!() + "::mempool.num_own_txs()");
1✔
2918
            state.mempool.num_own_txs()
1✔
2919
        };
1✔
2920
        let cpu_temp = None; // disable for now.  call is too slow.
1✔
2921
        let proving_capability = self.state.cli().proving_capability();
1✔
2922

1✔
2923
        info!("proving capability: {proving_capability}");
1✔
2924

2925
        let peer_count = Some(state.net.peer_map.len());
1✔
2926
        let max_num_peers = self.state.cli().max_num_peers;
1✔
2927

1✔
2928
        let mining_status = Some(state.mining_state.mining_status);
1✔
2929

2930
        let confirmations = {
1✔
2931
            log_slow_scope!(fn_name!() + "::confirmations_internal()");
1✔
2932
            self.confirmations_internal(&state).await
1✔
2933
        };
2934

2935
        let wallet_status = {
1✔
2936
            log_slow_scope!(fn_name!() + "::get_wallet_status_for_tip()");
1✔
2937
            state.get_wallet_status_for_tip().await
1✔
2938
        };
2939
        let wallet_state = &state.wallet_state;
1✔
2940

1✔
2941
        let confirmed_available_balance = {
1✔
2942
            log_slow_scope!(fn_name!() + "::confirmed_available_balance()");
1✔
2943
            wallet_state.confirmed_available_balance(&wallet_status, now)
1✔
2944
        };
1✔
2945
        let confirmed_total_balance = {
1✔
2946
            log_slow_scope!(fn_name!() + "::confirmed_total_balance()");
1✔
2947
            wallet_state.confirmed_total_balance(&wallet_status)
1✔
2948
        };
1✔
2949

1✔
2950
        let unconfirmed_available_balance = {
1✔
2951
            log_slow_scope!(fn_name!() + "::unconfirmed_available_balance()");
1✔
2952
            wallet_state.unconfirmed_available_balance(&wallet_status, now)
1✔
2953
        };
1✔
2954
        let unconfirmed_total_balance = {
1✔
2955
            log_slow_scope!(fn_name!() + "::unconfirmed_total_balance()");
1✔
2956
            wallet_state.unconfirmed_total_balance(&wallet_status)
1✔
2957
        };
1✔
2958

1✔
2959
        Ok(DashBoardOverviewDataFromClient {
1✔
2960
            tip_digest,
1✔
2961
            tip_header,
1✔
2962
            syncing,
1✔
2963
            confirmed_available_balance,
1✔
2964
            confirmed_total_balance,
1✔
2965
            unconfirmed_available_balance,
1✔
2966
            unconfirmed_total_balance,
1✔
2967
            mempool_size,
1✔
2968
            mempool_total_tx_count,
1✔
2969
            mempool_own_tx_count,
1✔
2970
            peer_count,
1✔
2971
            max_num_peers,
1✔
2972
            mining_status,
1✔
2973
            proving_capability,
1✔
2974
            confirmations,
1✔
2975
            cpu_temp,
1✔
2976
        })
1✔
2977
    }
1✔
2978

2979
    /******** CHANGE THINGS ********/
2980
    // Locking:
2981
    //   * acquires `global_state_lock` for write
2982
    //
2983
    // documented in trait. do not add doc-comment.
2984
    async fn clear_all_standings(
2✔
2985
        mut self,
2✔
2986
        _: context::Context,
2✔
2987
        token: rpc_auth::Token,
2✔
2988
    ) -> RpcResult<()> {
2✔
2989
        log_slow_scope!(fn_name!());
2✔
2990
        token.auth(&self.valid_tokens)?;
2✔
2991

2992
        let mut global_state_mut = self.state.lock_guard_mut().await;
2✔
2993
        global_state_mut
2✔
2994
            .net
2✔
2995
            .peer_map
2✔
2996
            .iter_mut()
2✔
2997
            .for_each(|(_, peerinfo)| {
4✔
2998
                peerinfo.standing.clear_standing();
4✔
2999
            });
4✔
3000

2✔
3001
        // iterates and modifies standing field for all connected peers
2✔
3002
        global_state_mut.net.clear_all_standings_in_database().await;
2✔
3003

3004
        Ok(global_state_mut.flush_databases().await?)
2✔
3005
    }
2✔
3006

3007
    // Locking:
3008
    //   * acquires `global_state_lock` for write
3009
    //
3010
    // documented in trait. do not add doc-comment.
3011
    async fn clear_standing_by_ip(
2✔
3012
        mut self,
2✔
3013
        _: context::Context,
2✔
3014
        token: rpc_auth::Token,
2✔
3015
        ip: IpAddr,
2✔
3016
    ) -> RpcResult<()> {
2✔
3017
        log_slow_scope!(fn_name!());
2✔
3018
        token.auth(&self.valid_tokens)?;
2✔
3019

3020
        let mut global_state_mut = self.state.lock_guard_mut().await;
2✔
3021
        global_state_mut
2✔
3022
            .net
2✔
3023
            .peer_map
2✔
3024
            .iter_mut()
2✔
3025
            .for_each(|(socketaddr, peerinfo)| {
4✔
3026
                if socketaddr.ip() == ip {
4✔
3027
                    peerinfo.standing.clear_standing();
1✔
3028
                }
3✔
3029
            });
4✔
3030

2✔
3031
        //Also clears this IP's standing in database, whether it is connected or not.
2✔
3032
        global_state_mut.net.clear_ip_standing_in_database(ip).await;
2✔
3033

3034
        Ok(global_state_mut.flush_databases().await?)
2✔
3035
    }
2✔
3036

3037
    // documented in trait. do not add doc-comment.
3038
    async fn send(
2✔
3039
        self,
2✔
3040
        ctx: context::Context,
2✔
3041
        token: rpc_auth::Token,
2✔
3042
        amount: NativeCurrencyAmount,
2✔
3043
        address: ReceivingAddress,
2✔
3044
        owned_utxo_notify_method: UtxoNotificationMedium,
2✔
3045
        unowned_utxo_notify_medium: UtxoNotificationMedium,
2✔
3046
        fee: NativeCurrencyAmount,
2✔
3047
    ) -> RpcResult<(TransactionKernelId, Vec<PrivateNotificationData>)> {
2✔
3048
        log_slow_scope!(fn_name!());
2✔
3049

2✔
3050
        // note: we do not call token.auth() because send_to_many() does it.
2✔
3051

2✔
3052
        self.send_to_many(
2✔
3053
            ctx,
2✔
3054
            token,
2✔
3055
            vec![(address, amount)],
2✔
3056
            owned_utxo_notify_method,
2✔
3057
            unowned_utxo_notify_medium,
2✔
3058
            fee,
2✔
3059
        )
2✔
3060
        .await
2✔
3061
    }
2✔
3062

3063
    // Locking:
3064
    //   * acquires `global_state_lock` for write
3065
    //
3066
    // TODO: add an endpoint to get recommended fee density.
3067
    //
3068
    // documented in trait. do not add doc-comment.
3069
    async fn send_to_many(
3✔
3070
        self,
3✔
3071
        ctx: context::Context,
3✔
3072
        token: rpc_auth::Token,
3✔
3073
        outputs: Vec<(ReceivingAddress, NativeCurrencyAmount)>,
3✔
3074
        owned_utxo_notification_medium: UtxoNotificationMedium,
3✔
3075
        unowned_utxo_notification_medium: UtxoNotificationMedium,
3✔
3076
        fee: NativeCurrencyAmount,
3✔
3077
    ) -> RpcResult<(TransactionKernelId, Vec<PrivateNotificationData>)> {
3✔
3078
        log_slow_scope!(fn_name!());
3✔
3079
        token.auth(&self.valid_tokens)?;
3✔
3080

3081
        tracing::debug!("stm: entered fn");
3✔
3082

3083
        if self.state.cli().no_transaction_initiation {
3✔
3084
            warn!("Cannot initiate transaction because `--no-transaction-initiation` flag is set.");
2✔
3085
            return Err(error::SendError::Unsupported.into());
2✔
3086
        }
1✔
3087

1✔
3088
        // abort early on negative fee
1✔
3089
        if fee.is_negative() {
1✔
3090
            warn!("Cannot send negative-fee transaction.");
×
3091
            return Err(error::SendError::NegativeFee.into());
×
3092
        }
1✔
3093

1✔
3094
        match self.state.cli().proving_capability() {
1✔
3095
            TxProvingCapability::LockScript | TxProvingCapability::PrimitiveWitness => {
3096
                warn!("Cannot initiate transaction because transaction proving capability is too weak.");
1✔
3097
                return Err(error::SendError::TooWeak.into());
1✔
3098
            }
3099
            TxProvingCapability::ProofCollection | TxProvingCapability::SingleProof => (),
×
3100
        };
×
3101

×
3102
        // The proving capability is set to the lowest possible value here,
×
3103
        // since we don't want the client (CLI or dashboard) to hang while
×
3104
        // producing proofs. Instead, we let (a task started by) main loop
×
3105
        // handle the proving.
×
3106
        let tx_proving_capability = TxProvingCapability::PrimitiveWitness;
×
3107
        Ok(self
×
3108
            .send_to_many_inner(
×
3109
                ctx,
×
3110
                outputs,
×
3111
                (
×
3112
                    owned_utxo_notification_medium,
×
3113
                    unowned_utxo_notification_medium,
×
3114
                ),
×
3115
                fee,
×
3116
                Timestamp::now(),
×
3117
                tx_proving_capability,
×
3118
            )
×
3119
            .await?)
×
3120
    }
3✔
3121

3122
    // // documented in trait. do not add doc-comment.
3123
    async fn claim_utxo(
14✔
3124
        mut self,
14✔
3125
        _ctx: context::Context,
14✔
3126
        token: rpc_auth::Token,
14✔
3127
        encrypted_utxo_notification: String,
14✔
3128
        max_search_depth: Option<u64>,
14✔
3129
    ) -> RpcResult<bool> {
14✔
3130
        log_slow_scope!(fn_name!());
14✔
3131
        token.auth(&self.valid_tokens)?;
14✔
3132

3133
        let claim_data = self
14✔
3134
            .claim_utxo_inner(encrypted_utxo_notification, max_search_depth)
14✔
3135
            .await?;
14✔
3136

3137
        let Some(claim_data) = claim_data else {
14✔
3138
            // UTXO has already been claimed by wallet
3139
            warn!("UTXO notification of amount was already received. Not adding again.");
6✔
3140
            return Ok(false);
6✔
3141
        };
3142

3143
        let expected_utxo_was_new = !claim_data.has_expected_utxo;
8✔
3144
        self.state
8✔
3145
            .lock_guard_mut()
8✔
3146
            .await
8✔
3147
            .wallet_state
3148
            .claim_utxo(claim_data)
8✔
3149
            .await
8✔
3150
            .map_err(error::ClaimError::from)?;
8✔
3151

3152
        Ok(expected_utxo_was_new)
8✔
3153
    }
14✔
3154

3155
    // documented in trait. do not add doc-comment.
3156
    async fn shutdown(self, _: context::Context, token: rpc_auth::Token) -> RpcResult<bool> {
1✔
3157
        log_slow_scope!(fn_name!());
1✔
3158
        token.auth(&self.valid_tokens)?;
1✔
3159

3160
        // 1. Send shutdown message to main
3161
        let response = self
1✔
3162
            .rpc_server_to_main_tx
1✔
3163
            .send(RPCServerToMain::Shutdown)
1✔
3164
            .await;
1✔
3165

3166
        // 2. Send acknowledgement to client.
3167
        Ok(response.is_ok())
1✔
3168
    }
1✔
3169

3170
    // documented in trait. do not add doc-comment.
3171
    async fn pause_miner(
1✔
3172
        self,
1✔
3173
        _context: tarpc::context::Context,
1✔
3174
        token: rpc_auth::Token,
1✔
3175
    ) -> RpcResult<()> {
1✔
3176
        log_slow_scope!(fn_name!());
1✔
3177
        token.auth(&self.valid_tokens)?;
1✔
3178

3179
        if self.state.cli().mine() {
1✔
3180
            let _ = self
×
3181
                .rpc_server_to_main_tx
×
3182
                .send(RPCServerToMain::PauseMiner)
×
3183
                .await;
×
3184
        } else {
3185
            info!("Cannot pause miner since it was never started");
1✔
3186
        }
3187
        Ok(())
1✔
3188
    }
1✔
3189

3190
    // documented in trait. do not add doc-comment.
3191
    async fn restart_miner(
1✔
3192
        self,
1✔
3193
        _context: tarpc::context::Context,
1✔
3194
        token: rpc_auth::Token,
1✔
3195
    ) -> RpcResult<()> {
1✔
3196
        log_slow_scope!(fn_name!());
1✔
3197
        token.auth(&self.valid_tokens)?;
1✔
3198

3199
        if self.state.cli().mine() {
1✔
3200
            let _ = self
×
3201
                .rpc_server_to_main_tx
×
3202
                .send(RPCServerToMain::RestartMiner)
×
3203
                .await;
×
3204
        } else {
3205
            info!("Cannot restart miner since it was never started");
1✔
3206
        }
3207
        Ok(())
1✔
3208
    }
1✔
3209

3210
    // documented in trait. do not add doc-comment.
3211
    async fn provide_pow_solution(
6✔
3212
        self,
6✔
3213
        _context: tarpc::context::Context,
6✔
3214
        token: rpc_auth::Token,
6✔
3215
        nonce: Digest,
6✔
3216
        proposal_id: Digest,
6✔
3217
    ) -> RpcResult<bool> {
6✔
3218
        log_slow_scope!(fn_name!());
6✔
3219
        token.auth(&self.valid_tokens)?;
6✔
3220

3221
        // Find proposal from list of exported proposals.
3222
        let Some(mut proposal) = self
6✔
3223
            .state
6✔
3224
            .lock_guard()
6✔
3225
            .await
6✔
3226
            .mining_state
3227
            .exported_block_proposals
3228
            .get(&proposal_id)
6✔
3229
            .map(|x| x.to_owned())
6✔
3230
        else {
3231
            warn!(
2✔
3232
                "Got claimed PoW solution but no challenge was known. \
×
3233
                Did solution come in too late?"
×
3234
            );
3235
            return Ok(false);
2✔
3236
        };
3237

3238
        // A proposal was found. Check if solution works.
3239
        let latest_block_header = *self.state.lock_guard().await.chain.light_state().header();
4✔
3240

4✔
3241
        proposal.set_header_nonce(nonce);
4✔
3242
        let threshold = latest_block_header.difficulty.target();
4✔
3243
        let solution_digest = proposal.hash();
4✔
3244
        if solution_digest > threshold {
4✔
3245
            warn!(
2✔
3246
                "Got claimed PoW solution but PoW threshold was not met.\n\
×
3247
            Claimed solution: {solution_digest};\nthreshold: {threshold}"
×
3248
            );
3249
            return Ok(false);
2✔
3250
        }
2✔
3251

2✔
3252
        // No time to waste! Inform main_loop!
2✔
3253
        let solution = Box::new(proposal);
2✔
3254
        let _ = self
2✔
3255
            .rpc_server_to_main_tx
2✔
3256
            .send(RPCServerToMain::ProofOfWorkSolution(solution))
2✔
3257
            .await;
2✔
3258

3259
        Ok(true)
2✔
3260
    }
6✔
3261

3262
    // documented in trait. do not add doc-comment.
3263
    async fn prune_abandoned_monitored_utxos(
1✔
3264
        mut self,
1✔
3265
        _context: tarpc::context::Context,
1✔
3266
        token: rpc_auth::Token,
1✔
3267
    ) -> RpcResult<usize> {
1✔
3268
        const DEFAULT_MUTXO_PRUNE_DEPTH: usize = 200;
3269

3270
        log_slow_scope!(fn_name!());
1✔
3271
        token.auth(&self.valid_tokens)?;
1✔
3272

3273
        let mut global_state_mut = self.state.lock_guard_mut().await;
1✔
3274

3275
        let prune_count_res = global_state_mut
1✔
3276
            .prune_abandoned_monitored_utxos(DEFAULT_MUTXO_PRUNE_DEPTH)
1✔
3277
            .await;
1✔
3278

3279
        global_state_mut
1✔
3280
            .flush_databases()
1✔
3281
            .await
1✔
3282
            .expect("flushed DBs");
1✔
3283

1✔
3284
        match prune_count_res {
1✔
3285
            Ok(prune_count) => {
1✔
3286
                info!("Marked {prune_count} monitored UTXOs as abandoned");
1✔
3287
                Ok(prune_count)
1✔
3288
            }
3289
            Err(err) => {
×
3290
                error!("Pruning monitored UTXOs failed with error: {err}");
×
3291
                Ok(0)
×
3292
            }
3293
        }
3294
    }
1✔
3295

3296
    // documented in trait. do not add doc-comment.
3297
    async fn list_own_coins(
×
3298
        self,
×
3299
        _context: ::tarpc::context::Context,
×
3300
        token: rpc_auth::Token,
×
3301
    ) -> RpcResult<Vec<CoinWithPossibleTimeLock>> {
×
3302
        log_slow_scope!(fn_name!());
×
3303
        token.auth(&self.valid_tokens)?;
×
3304

3305
        let state = self.state.lock_guard().await;
×
3306
        let tip = state.chain.light_state();
×
3307
        let tip_hash = tip.hash();
×
3308
        let tip_msa = tip.mutator_set_accumulator_after();
×
3309

×
3310
        Ok(state
×
3311
            .wallet_state
×
3312
            .get_all_own_coins_with_possible_timelocks(&tip_msa, tip_hash)
×
3313
            .await)
×
3314
    }
×
3315

3316
    // documented in trait. do not add doc-comment.
3317
    async fn cpu_temp(
1✔
3318
        self,
1✔
3319
        _context: tarpc::context::Context,
1✔
3320
        token: rpc_auth::Token,
1✔
3321
    ) -> RpcResult<Option<f32>> {
1✔
3322
        log_slow_scope!(fn_name!());
1✔
3323
        token.auth(&self.valid_tokens)?;
1✔
3324

3325
        Ok(Self::cpu_temp_inner())
1✔
3326
    }
1✔
3327

3328
    // documented in trait. do not add doc-comment.
3329
    async fn pow_puzzle_internal_key(
2✔
3330
        self,
2✔
3331
        _context: tarpc::context::Context,
2✔
3332
        token: rpc_auth::Token,
2✔
3333
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
2✔
3334
        log_slow_scope!(fn_name!());
2✔
3335
        token.auth(&self.valid_tokens)?;
2✔
3336

3337
        let Some(proposal) = self
2✔
3338
            .state
2✔
3339
            .lock_guard()
2✔
3340
            .await
2✔
3341
            .mining_state
3342
            .block_proposal
3343
            .map(|x| x.to_owned())
2✔
3344
        else {
3345
            return Ok(None);
1✔
3346
        };
3347

3348
        let guesser_key_after_image = self
1✔
3349
            .state
1✔
3350
            .lock_guard()
1✔
3351
            .await
1✔
3352
            .wallet_state
3353
            .wallet_entropy
3354
            .guesser_spending_key(proposal.header().prev_block_digest)
1✔
3355
            .after_image();
1✔
3356

1✔
3357
        self.pow_puzzle_inner(guesser_key_after_image, proposal)
1✔
3358
            .await
1✔
3359
    }
2✔
3360

3361
    // documented in trait. do not add doc-comment.
3362
    async fn pow_puzzle_external_key(
14✔
3363
        self,
14✔
3364
        _context: tarpc::context::Context,
14✔
3365
        token: rpc_auth::Token,
14✔
3366
        guesser_digest: Digest,
14✔
3367
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
14✔
3368
        log_slow_scope!(fn_name!());
14✔
3369
        token.auth(&self.valid_tokens)?;
14✔
3370

3371
        let Some(proposal) = self
14✔
3372
            .state
14✔
3373
            .lock_guard()
14✔
3374
            .await
14✔
3375
            .mining_state
3376
            .block_proposal
3377
            .map(|x| x.to_owned())
14✔
3378
        else {
3379
            return Ok(None);
1✔
3380
        };
3381

3382
        self.pow_puzzle_inner(guesser_digest, proposal).await
13✔
3383
    }
14✔
3384

3385
    // documented in trait. do not add doc-comment.
3386
    async fn block_intervals(
1✔
3387
        self,
1✔
3388
        _context: tarpc::context::Context,
1✔
3389
        token: rpc_auth::Token,
1✔
3390
        last_block: BlockSelector,
1✔
3391
        max_num_blocks: Option<usize>,
1✔
3392
    ) -> RpcResult<Option<Vec<(u64, u64)>>> {
1✔
3393
        log_slow_scope!(fn_name!());
1✔
3394
        token.auth(&self.valid_tokens)?;
1✔
3395

3396
        let state = self.state.lock_guard().await;
1✔
3397
        let Some(last_block) = last_block.as_digest(&state).await else {
1✔
3398
            return Ok(None);
×
3399
        };
3400
        let mut intervals = vec![];
1✔
3401
        let mut current = state
1✔
3402
            .chain
1✔
3403
            .archival_state()
1✔
3404
            .get_block_header(last_block)
1✔
3405
            .await
1✔
3406
            .expect("If digest can be found, block header should also be known");
1✔
3407
        let mut parent = state
1✔
3408
            .chain
1✔
3409
            .archival_state()
1✔
3410
            .get_block_header(current.prev_block_digest)
1✔
3411
            .await;
1✔
3412

3413
        // Exclude genesis since it was not mined. So block interval 0-->1
3414
        // is not included.
3415
        while parent.is_some()
1✔
3416
            && !parent.unwrap().height.is_genesis()
×
3417
            && max_num_blocks.is_none_or(|max_num| max_num > intervals.len())
×
3418
        {
3419
            let parent_ = parent.unwrap();
×
3420
            let interval = current.timestamp.to_millis() - parent_.timestamp.to_millis();
×
3421
            let block_height: u64 = current.height.into();
×
3422
            intervals.push((block_height, interval));
×
3423
            current = parent_;
×
3424
            parent = state
×
3425
                .chain
×
3426
                .archival_state()
×
3427
                .get_block_header(current.prev_block_digest)
×
3428
                .await;
×
3429
        }
3430

3431
        Ok(Some(intervals))
1✔
3432
    }
1✔
3433

3434
    async fn block_difficulties(
1✔
3435
        self,
1✔
3436
        _context: tarpc::context::Context,
1✔
3437
        token: rpc_auth::Token,
1✔
3438
        last_block: BlockSelector,
1✔
3439
        max_num_blocks: Option<usize>,
1✔
3440
    ) -> RpcResult<Vec<(u64, Difficulty)>> {
1✔
3441
        log_slow_scope!(fn_name!());
1✔
3442
        token.auth(&self.valid_tokens)?;
1✔
3443

3444
        let state = self.state.lock_guard().await;
1✔
3445
        let last_block = last_block.as_digest(&state).await;
1✔
3446
        let Some(last_block) = last_block else {
1✔
3447
            return Ok(vec![]);
×
3448
        };
3449

3450
        let mut difficulties = vec![];
1✔
3451

3452
        let mut current = state
1✔
3453
            .chain
1✔
3454
            .archival_state()
1✔
3455
            .get_block_header(last_block)
1✔
3456
            .await;
1✔
3457
        while current.is_some()
2✔
3458
            && max_num_blocks.is_none_or(|max_num| max_num >= difficulties.len())
1✔
3459
        {
3460
            let current_ = current.unwrap();
1✔
3461
            let height: u64 = current_.height.into();
1✔
3462
            difficulties.push((height, current_.difficulty));
1✔
3463
            current = state
1✔
3464
                .chain
1✔
3465
                .archival_state()
1✔
3466
                .get_block_header(current_.prev_block_digest)
1✔
3467
                .await;
1✔
3468
        }
3469

3470
        Ok(difficulties)
1✔
3471
    }
1✔
3472

3473
    // documented in trait. do not add doc-comment.
3474
    async fn broadcast_all_mempool_txs(
1✔
3475
        self,
1✔
3476
        _context: tarpc::context::Context,
1✔
3477
        token: rpc_auth::Token,
1✔
3478
    ) -> RpcResult<()> {
1✔
3479
        log_slow_scope!(fn_name!());
1✔
3480
        token.auth(&self.valid_tokens)?;
1✔
3481

3482
        // If this sending fails, it means `main_loop` is no longer running,
3483
        // and node is crashed. No reason to log anything additional.
3484
        let _ = self
1✔
3485
            .rpc_server_to_main_tx
1✔
3486
            .send(RPCServerToMain::BroadcastMempoolTransactions)
1✔
3487
            .await;
1✔
3488

3489
        Ok(())
1✔
3490
    }
1✔
3491

3492
    // documented in trait. do not add doc-comment.
3493
    async fn mempool_overview(
1✔
3494
        self,
1✔
3495
        _context: ::tarpc::context::Context,
1✔
3496
        token: rpc_auth::Token,
1✔
3497
        start_index: usize,
1✔
3498
        number: usize,
1✔
3499
    ) -> RpcResult<Vec<MempoolTransactionInfo>> {
1✔
3500
        log_slow_scope!(fn_name!());
1✔
3501
        token.auth(&self.valid_tokens)?;
1✔
3502

3503
        let global_state = self.state.lock_guard().await;
1✔
3504
        let mempool_txkids = global_state
1✔
3505
            .mempool
1✔
3506
            .get_sorted_iter()
1✔
3507
            .skip(start_index)
1✔
3508
            .take(number)
1✔
3509
            .map(|(txkid, _)| txkid)
1✔
3510
            .collect_vec();
1✔
3511

1✔
3512
        let (incoming, outgoing): (HashMap<_, _>, HashMap<_, _>) = {
1✔
3513
            let (incoming_iter, outgoing_iter) =
1✔
3514
                global_state.wallet_state.mempool_balance_updates();
1✔
3515
            (incoming_iter.collect(), outgoing_iter.collect())
1✔
3516
        };
1✔
3517

1✔
3518
        let tip_msah = global_state
1✔
3519
            .chain
1✔
3520
            .light_state()
1✔
3521
            .mutator_set_accumulator_after()
1✔
3522
            .hash();
1✔
3523

1✔
3524
        let mempool_transactions = mempool_txkids
1✔
3525
            .iter()
1✔
3526
            .filter_map(|id| {
1✔
3527
                let mut mptxi = global_state
×
3528
                    .mempool
×
3529
                    .get(*id)
×
3530
                    .map(|tx| (MempoolTransactionInfo::from(tx), tx.kernel.mutator_set_hash))
×
3531
                    .map(|(mptxi, tx_msah)| {
×
3532
                        if tx_msah == tip_msah {
×
3533
                            mptxi.synced()
×
3534
                        } else {
3535
                            mptxi
×
3536
                        }
3537
                    });
×
3538
                if mptxi.is_some() {
×
3539
                    if let Some(pos_effect) = incoming.get(id) {
×
3540
                        mptxi = Some(mptxi.unwrap().with_positive_effect_on_balance(*pos_effect));
×
3541
                    }
×
3542
                    if let Some(neg_effect) = outgoing.get(id) {
×
3543
                        mptxi = Some(mptxi.unwrap().with_negative_effect_on_balance(*neg_effect));
×
3544
                    }
×
3545
                }
×
3546

3547
                mptxi
×
3548
            })
1✔
3549
            .collect_vec();
1✔
3550

1✔
3551
        Ok(mempool_transactions)
1✔
3552
    }
1✔
3553
}
3554

3555
pub mod error {
3556
    use super::*;
3557

3558
    /// enumerates possible rpc api errors
3559
    #[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
3560
    #[non_exhaustive]
3561
    pub enum RpcError {
3562
        // auth error
3563
        #[error(transparent)]
3564
        Auth(#[from] rpc_auth::error::AuthError),
3565

3566
        // catch-all error, eg for anyhow errors
3567
        #[error("rpc call failed")]
3568
        Failed(String),
3569

3570
        // API specific error variants.
3571
        #[error("cookie hints are disabled on this node")]
3572
        CookieHintDisabled,
3573

3574
        #[error("capacity to store exported block proposals exceeded")]
3575
        ExportedBlockProposalStorageCapacityExceeded,
3576

3577
        #[error(transparent)]
3578
        SendError(#[from] SendError),
3579

3580
        #[error(transparent)]
3581
        ClaimError(#[from] ClaimError),
3582
    }
3583

3584
    // convert anyhow::Error to an RpcError::Failed.
3585
    // note that anyhow Error is not serializable.
3586
    impl From<anyhow::Error> for RpcError {
3587
        fn from(e: anyhow::Error) -> Self {
×
3588
            Self::Failed(e.to_string())
×
3589
        }
×
3590
    }
3591

3592
    /// enumerates possible transaction send errors
3593
    #[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
3594
    #[non_exhaustive]
3595
    pub enum SendError {
3596
        #[error("send() is not supported by this node")]
3597
        Unsupported,
3598

3599
        #[error("transaction could not be broadcast.")]
3600
        NotBroadcast,
3601

3602
        // catch-all error, eg for anyhow errors
3603
        #[error("transaction could not be sent.  reason: {0}")]
3604
        Failed(String),
3605

3606
        #[error("Transaction with negative fees not allowed")]
3607
        NegativeFee,
3608

3609
        #[error("machine too weak to initiate transactions")]
3610
        TooWeak,
3611

3612
        #[error("Send rate limit reached for block height {height} ({digest}). A maximum of {max} tx may be sent per block.", digest = tip_digest.to_hex())]
3613
        RateLimit {
3614
            height: BlockHeight,
3615
            tip_digest: Digest,
3616
            max: usize,
3617
        },
3618
    }
3619

3620
    // convert anyhow::Error to a SendError::Failed.
3621
    // note that anyhow Error is not serializable.
3622
    impl From<anyhow::Error> for SendError {
3623
        fn from(e: anyhow::Error) -> Self {
1✔
3624
            Self::Failed(e.to_string())
1✔
3625
        }
1✔
3626
    }
3627

3628
    /// enumerates possible transaction send errors
3629
    #[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
3630
    #[non_exhaustive]
3631
    pub enum ClaimError {
3632
        #[error("utxo does not match any known wallet key")]
3633
        UtxoUnknown,
3634

3635
        #[error("invalid type script in claim utxo")]
3636
        InvalidTypeScript,
3637

3638
        // catch-all error, eg for anyhow errors
3639
        #[error("claim unsuccessful")]
3640
        Failed(String),
3641
    }
3642

3643
    // convert anyhow::Error to a ClaimError::Failed.
3644
    // note that anyhow Error is not serializable.
3645
    impl From<anyhow::Error> for ClaimError {
3646
        fn from(e: anyhow::Error) -> Self {
×
3647
            Self::Failed(e.to_string())
×
3648
        }
×
3649
    }
3650
}
3651

3652
#[cfg(test)]
3653
mod rpc_server_tests {
3654
    use anyhow::Result;
3655
    use num_traits::One;
3656
    use num_traits::Zero;
3657
    use rand::rngs::StdRng;
3658
    use rand::Rng;
3659
    use rand::SeedableRng;
3660
    use strum::IntoEnumIterator;
3661
    use tracing_test::traced_test;
3662

3663
    use super::*;
3664
    use crate::config_models::cli_args;
3665
    use crate::config_models::network::Network;
3666
    use crate::database::storage::storage_vec::traits::*;
3667
    use crate::models::blockchain::transaction::transaction_kernel::transaction_kernel_tests::pseudorandom_transaction_kernel;
3668
    use crate::models::peer::NegativePeerSanction;
3669
    use crate::models::peer::PeerSanction;
3670
    use crate::models::state::wallet::address::generation_address::GenerationSpendingKey;
3671
    use crate::models::state::wallet::wallet_entropy::WalletEntropy;
3672
    use crate::rpc_server::NeptuneRPCServer;
3673
    use crate::tests::shared::invalid_block_with_transaction;
3674
    use crate::tests::shared::make_mock_block;
3675
    use crate::tests::shared::mock_genesis_global_state;
3676
    use crate::tests::shared::unit_test_data_directory;
3677
    use crate::Block;
3678
    use crate::RPC_CHANNEL_CAPACITY;
3679

3680
    async fn test_rpc_server(
29✔
3681
        network: Network,
29✔
3682
        wallet_entropy: WalletEntropy,
29✔
3683
        peer_count: u8,
29✔
3684
        cli: cli_args::Args,
29✔
3685
    ) -> NeptuneRPCServer {
29✔
3686
        let global_state_lock =
29✔
3687
            mock_genesis_global_state(network, peer_count, wallet_entropy, cli).await;
29✔
3688
        let (dummy_tx, mut dummy_rx) =
29✔
3689
            tokio::sync::mpsc::channel::<RPCServerToMain>(RPC_CHANNEL_CAPACITY);
29✔
3690

29✔
3691
        tokio::spawn(async move {
29✔
3692
            while let Some(i) = dummy_rx.recv().await {
40✔
3693
                tracing::trace!("mock Main got message = {:?}", i);
11✔
3694
            }
3695
        });
29✔
3696

29✔
3697
        let data_directory = unit_test_data_directory(network).unwrap();
29✔
3698

3699
        let valid_tokens: Vec<rpc_auth::Token> = vec![rpc_auth::Cookie::try_new(&data_directory)
29✔
3700
            .await
29✔
3701
            .unwrap()
29✔
3702
            .into()];
29✔
3703

29✔
3704
        NeptuneRPCServer::new(global_state_lock, dummy_tx, data_directory, valid_tokens)
29✔
3705
    }
29✔
3706

3707
    async fn cookie_token(server: &NeptuneRPCServer) -> rpc_auth::Token {
19✔
3708
        rpc_auth::Cookie::try_load(server.data_directory())
19✔
3709
            .await
19✔
3710
            .unwrap()
19✔
3711
            .into()
19✔
3712
    }
19✔
3713

3714
    #[tokio::test]
3715
    async fn network_response_is_consistent() -> Result<()> {
1✔
3716
        // Verify that a wallet not receiving a premine is empty at startup
1✔
3717
        for network in Network::iter() {
6✔
3718
            let rpc_server = test_rpc_server(
5✔
3719
                network,
5✔
3720
                WalletEntropy::new_random(),
5✔
3721
                2,
5✔
3722
                cli_args::Args {
5✔
3723
                    network,
5✔
3724
                    ..Default::default()
5✔
3725
                },
5✔
3726
            )
5✔
3727
            .await;
5✔
3728
            assert_eq!(network, rpc_server.network(context::current()).await?);
5✔
3729
        }
1✔
3730

1✔
3731
        Ok(())
1✔
3732
    }
1✔
3733

3734
    #[tokio::test]
3735
    async fn verify_that_all_requests_leave_server_running() -> Result<()> {
1✔
3736
        // Got through *all* request types and verify that server does not crash.
1✔
3737
        // We don't care about the actual response data in this test, just that the
1✔
3738
        // requests do not crash the server.
1✔
3739

1✔
3740
        let network = Network::Main;
1✔
3741
        let mut rng = StdRng::seed_from_u64(123456789088u64);
1✔
3742

1✔
3743
        let rpc_server = test_rpc_server(
1✔
3744
            network,
1✔
3745
            WalletEntropy::new_pseudorandom(rng.random()),
1✔
3746
            2,
1✔
3747
            cli_args::Args::default(),
1✔
3748
        )
1✔
3749
        .await;
1✔
3750
        let token = cookie_token(&rpc_server).await;
1✔
3751
        let ctx = context::current();
1✔
3752
        let _ = rpc_server.clone().network(ctx).await;
1✔
3753
        let _ = rpc_server
1✔
3754
            .clone()
1✔
3755
            .own_listen_address_for_peers(ctx, token)
1✔
3756
            .await;
1✔
3757
        let _ = rpc_server.clone().own_instance_id(ctx, token).await;
1✔
3758
        let _ = rpc_server.clone().block_height(ctx, token).await;
1✔
3759
        let _ = rpc_server.clone().peer_info(ctx, token).await;
1✔
3760
        let _ = rpc_server
1✔
3761
            .clone()
1✔
3762
            .block_digests_by_height(ctx, token, 42u64.into())
1✔
3763
            .await;
1✔
3764
        let _ = rpc_server
1✔
3765
            .clone()
1✔
3766
            .block_digests_by_height(ctx, token, 0u64.into())
1✔
3767
            .await;
1✔
3768
        let _ = rpc_server.clone().all_punished_peers(ctx, token).await;
1✔
3769
        let _ = rpc_server.clone().latest_tip_digests(ctx, token, 2).await;
1✔
3770
        let _ = rpc_server
1✔
3771
            .clone()
1✔
3772
            .header(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3773
            .await;
1✔
3774
        let _ = rpc_server
1✔
3775
            .clone()
1✔
3776
            .block_info(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3777
            .await;
1✔
3778
        let _ = rpc_server
1✔
3779
            .clone()
1✔
3780
            .public_announcements_in_block(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3781
            .await;
1✔
3782
        let _ = rpc_server
1✔
3783
            .clone()
1✔
3784
            .block_digest(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3785
            .await;
1✔
3786
        let _ = rpc_server.clone().utxo_digest(ctx, token, 0).await;
1✔
3787
        let _ = rpc_server
1✔
3788
            .clone()
1✔
3789
            .confirmed_available_balance(ctx, token)
1✔
3790
            .await;
1✔
3791
        let _ = rpc_server.clone().history(ctx, token).await;
1✔
3792
        let _ = rpc_server.clone().wallet_status(ctx, token).await;
1✔
3793
        let own_receiving_address = rpc_server
1✔
3794
            .clone()
1✔
3795
            .next_receiving_address(ctx, token, KeyType::Generation)
1✔
3796
            .await?
1✔
3797
            .unwrap();
1✔
3798
        let _ = rpc_server.clone().mempool_tx_count(ctx, token).await;
1✔
3799
        let _ = rpc_server.clone().mempool_size(ctx, token).await;
1✔
3800
        let _ = rpc_server.clone().dashboard_overview_data(ctx, token).await;
1✔
3801
        let _ = rpc_server
1✔
3802
            .clone()
1✔
3803
            .validate_address(
1✔
3804
                ctx,
1✔
3805
                token,
1✔
3806
                "Not a valid address".to_owned(),
1✔
3807
                Network::Testnet,
1✔
3808
            )
1✔
3809
            .await;
1✔
3810
        let _ = rpc_server.clone().pow_puzzle_internal_key(ctx, token).await;
1✔
3811
        let _ = rpc_server
1✔
3812
            .clone()
1✔
3813
            .pow_puzzle_external_key(ctx, token, rng.random())
1✔
3814
            .await;
1✔
3815
        let _ = rpc_server
1✔
3816
            .clone()
1✔
3817
            .provide_pow_solution(ctx, token, rng.random(), rng.random())
1✔
3818
            .await;
1✔
3819
        let _ = rpc_server
1✔
3820
            .clone()
1✔
3821
            .block_intervals(ctx, token, BlockSelector::Tip, None)
1✔
3822
            .await;
1✔
3823
        let _ = rpc_server
1✔
3824
            .clone()
1✔
3825
            .block_difficulties(ctx, token, BlockSelector::Tip, None)
1✔
3826
            .await;
1✔
3827
        let _ = rpc_server
1✔
3828
            .clone()
1✔
3829
            .broadcast_all_mempool_txs(ctx, token)
1✔
3830
            .await;
1✔
3831
        let _ = rpc_server.clone().mempool_overview(ctx, token, 0, 20).await;
1✔
3832
        let _ = rpc_server.clone().clear_all_standings(ctx, token).await;
1✔
3833
        let _ = rpc_server
1✔
3834
            .clone()
1✔
3835
            .clear_standing_by_ip(ctx, token, "127.0.0.1".parse().unwrap())
1✔
3836
            .await;
1✔
3837
        let _ = rpc_server
1✔
3838
            .clone()
1✔
3839
            .send(
1✔
3840
                ctx,
1✔
3841
                token,
1✔
3842
                NativeCurrencyAmount::one(),
1✔
3843
                own_receiving_address.clone(),
1✔
3844
                UtxoNotificationMedium::OffChain,
1✔
3845
                UtxoNotificationMedium::OffChain,
1✔
3846
                NativeCurrencyAmount::one(),
1✔
3847
            )
1✔
3848
            .await;
1✔
3849

1✔
3850
        let transaction_timestamp = network.launch_date();
1✔
3851
        let proving_capability = rpc_server.state.cli().proving_capability();
1✔
3852
        let _ = rpc_server
1✔
3853
            .clone()
1✔
3854
            .send_to_many_inner(
1✔
3855
                ctx,
1✔
3856
                vec![(own_receiving_address, NativeCurrencyAmount::one())],
1✔
3857
                (
1✔
3858
                    UtxoNotificationMedium::OffChain,
1✔
3859
                    UtxoNotificationMedium::OffChain,
1✔
3860
                ),
1✔
3861
                NativeCurrencyAmount::one(),
1✔
3862
                transaction_timestamp,
1✔
3863
                proving_capability,
1✔
3864
            )
1✔
3865
            .await;
1✔
3866
        let _ = rpc_server.clone().pause_miner(ctx, token).await;
1✔
3867
        let _ = rpc_server.clone().restart_miner(ctx, token).await;
1✔
3868
        let _ = rpc_server
1✔
3869
            .clone()
1✔
3870
            .prune_abandoned_monitored_utxos(ctx, token)
1✔
3871
            .await;
1✔
3872
        let _ = rpc_server.shutdown(ctx, token).await;
1✔
3873

1✔
3874
        Ok(())
1✔
3875
    }
1✔
3876

3877
    #[traced_test]
×
3878
    #[tokio::test]
3879
    async fn balance_is_zero_at_init() -> Result<()> {
1✔
3880
        // Verify that a wallet not receiving a premine is empty at startup
1✔
3881
        let rpc_server = test_rpc_server(
1✔
3882
            Network::Alpha,
1✔
3883
            WalletEntropy::new_random(),
1✔
3884
            2,
1✔
3885
            cli_args::Args::default(),
1✔
3886
        )
1✔
3887
        .await;
1✔
3888
        let token = cookie_token(&rpc_server).await;
1✔
3889
        let balance = rpc_server
1✔
3890
            .confirmed_available_balance(context::current(), token)
1✔
3891
            .await?;
1✔
3892
        assert!(balance.is_zero());
1✔
3893

1✔
3894
        Ok(())
1✔
3895
    }
1✔
3896

3897
    #[allow(clippy::shadow_unrelated)]
3898
    #[traced_test]
×
3899
    #[tokio::test]
3900
    async fn clear_ip_standing_test() -> Result<()> {
1✔
3901
        let mut rpc_server = test_rpc_server(
1✔
3902
            Network::Alpha,
1✔
3903
            WalletEntropy::new_random(),
1✔
3904
            2,
1✔
3905
            cli_args::Args::default(),
1✔
3906
        )
1✔
3907
        .await;
1✔
3908
        let token = cookie_token(&rpc_server).await;
1✔
3909
        let rpc_request_context = context::current();
1✔
3910
        let (peer_address0, peer_address1) = {
1✔
3911
            let global_state = rpc_server.state.lock_guard().await;
1✔
3912

1✔
3913
            (
1✔
3914
                global_state.net.peer_map.values().collect::<Vec<_>>()[0].connected_address(),
1✔
3915
                global_state.net.peer_map.values().collect::<Vec<_>>()[1].connected_address(),
1✔
3916
            )
1✔
3917
        };
1✔
3918

1✔
3919
        // Verify that sanctions list is empty
1✔
3920
        let punished_peers_startup = rpc_server
1✔
3921
            .clone()
1✔
3922
            .all_punished_peers(rpc_request_context, token)
1✔
3923
            .await?;
1✔
3924
        assert!(
1✔
3925
            punished_peers_startup.is_empty(),
1✔
3926
            "Sanctions list must be empty at startup"
1✔
3927
        );
1✔
3928

1✔
3929
        // sanction both
1✔
3930
        let (standing0, standing1) = {
1✔
3931
            let mut global_state_mut = rpc_server.state.lock_guard_mut().await;
1✔
3932

1✔
3933
            global_state_mut
1✔
3934
                .net
1✔
3935
                .peer_map
1✔
3936
                .entry(peer_address0)
1✔
3937
                .and_modify(|p| {
1✔
3938
                    p.standing
1✔
3939
                        .sanction(PeerSanction::Negative(
1✔
3940
                            NegativePeerSanction::DifferentGenesis,
1✔
3941
                        ))
1✔
3942
                        .unwrap_err();
1✔
3943
                });
1✔
3944
            global_state_mut
1✔
3945
                .net
1✔
3946
                .peer_map
1✔
3947
                .entry(peer_address1)
1✔
3948
                .and_modify(|p| {
1✔
3949
                    p.standing
1✔
3950
                        .sanction(PeerSanction::Negative(
1✔
3951
                            NegativePeerSanction::DifferentGenesis,
1✔
3952
                        ))
1✔
3953
                        .unwrap_err();
1✔
3954
                });
1✔
3955
            let standing_0 = global_state_mut.net.peer_map[&peer_address0].standing;
1✔
3956
            let standing_1 = global_state_mut.net.peer_map[&peer_address1].standing;
1✔
3957
            (standing_0, standing_1)
1✔
3958
        };
1✔
3959

1✔
3960
        // Verify expected sanctions reading
1✔
3961
        let punished_peers_from_memory = rpc_server
1✔
3962
            .clone()
1✔
3963
            .all_punished_peers(rpc_request_context, token)
1✔
3964
            .await?;
1✔
3965
        assert_eq!(
1✔
3966
            2,
1✔
3967
            punished_peers_from_memory.len(),
1✔
3968
            "Punished list must have two elements after sanctionings"
1✔
3969
        );
1✔
3970

1✔
3971
        {
1✔
3972
            let mut global_state_mut = rpc_server.state.lock_guard_mut().await;
1✔
3973

1✔
3974
            global_state_mut
1✔
3975
                .net
1✔
3976
                .write_peer_standing_on_decrease(peer_address0.ip(), standing0)
1✔
3977
                .await;
1✔
3978
            global_state_mut
1✔
3979
                .net
1✔
3980
                .write_peer_standing_on_decrease(peer_address1.ip(), standing1)
1✔
3981
                .await;
1✔
3982
        }
1✔
3983

1✔
3984
        // Verify expected sanctions reading, after DB-write
1✔
3985
        let punished_peers_from_memory_and_db = rpc_server
1✔
3986
            .clone()
1✔
3987
            .all_punished_peers(rpc_request_context, token)
1✔
3988
            .await?;
1✔
3989
        assert_eq!(
1✔
3990
            2,
1✔
3991
            punished_peers_from_memory_and_db.len(),
1✔
3992
            "Punished list must have to elements after sanctionings and after DB write"
1✔
3993
        );
1✔
3994

1✔
3995
        // Verify expected initial conditions
1✔
3996
        {
1✔
3997
            let global_state = rpc_server.state.lock_guard().await;
1✔
3998
            let standing0 = global_state
1✔
3999
                .net
1✔
4000
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4001
                .await;
1✔
4002
            assert_ne!(0, standing0.unwrap().standing);
1✔
4003
            assert_ne!(None, standing0.unwrap().latest_punishment);
1✔
4004
            let peer_standing_1 = global_state
1✔
4005
                .net
1✔
4006
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4007
                .await;
1✔
4008
            assert_ne!(0, peer_standing_1.unwrap().standing);
1✔
4009
            assert_ne!(None, peer_standing_1.unwrap().latest_punishment);
1✔
4010
            drop(global_state);
1✔
4011

1✔
4012
            // Clear standing of #0
1✔
4013
            rpc_server
1✔
4014
                .clone()
1✔
4015
                .clear_standing_by_ip(rpc_request_context, token, peer_address0.ip())
1✔
4016
                .await?;
1✔
4017
        }
1✔
4018

1✔
4019
        // Verify expected resulting conditions in database
1✔
4020
        {
1✔
4021
            let global_state = rpc_server.state.lock_guard().await;
1✔
4022
            let standing0 = global_state
1✔
4023
                .net
1✔
4024
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4025
                .await;
1✔
4026
            assert_eq!(0, standing0.unwrap().standing);
1✔
4027
            assert_eq!(None, standing0.unwrap().latest_punishment);
1✔
4028
            let standing1 = global_state
1✔
4029
                .net
1✔
4030
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4031
                .await;
1✔
4032
            assert_ne!(0, standing1.unwrap().standing);
1✔
4033
            assert_ne!(None, standing1.unwrap().latest_punishment);
1✔
4034

1✔
4035
            // Verify expected resulting conditions in peer map
1✔
4036
            let standing0_from_memory = global_state.net.peer_map[&peer_address0].clone();
1✔
4037
            assert_eq!(0, standing0_from_memory.standing.standing);
1✔
4038
            let standing1_from_memory = global_state.net.peer_map[&peer_address1].clone();
1✔
4039
            assert_ne!(0, standing1_from_memory.standing.standing);
1✔
4040
        }
1✔
4041

1✔
4042
        // Verify expected sanctions reading, after one forgiveness
1✔
4043
        let punished_list_after_one_clear = rpc_server
1✔
4044
            .clone()
1✔
4045
            .all_punished_peers(rpc_request_context, token)
1✔
4046
            .await?;
1✔
4047
        assert!(
1✔
4048
            punished_list_after_one_clear.len().is_one(),
1✔
4049
            "Punished list must have to elements after sanctionings and after DB write"
1✔
4050
        );
1✔
4051

1✔
4052
        Ok(())
1✔
4053
    }
1✔
4054

4055
    #[allow(clippy::shadow_unrelated)]
4056
    #[traced_test]
×
4057
    #[tokio::test]
4058
    async fn clear_all_standings_test() -> Result<()> {
1✔
4059
        // Create initial conditions
1✔
4060
        let mut rpc_server = test_rpc_server(
1✔
4061
            Network::Alpha,
1✔
4062
            WalletEntropy::new_random(),
1✔
4063
            2,
1✔
4064
            cli_args::Args::default(),
1✔
4065
        )
1✔
4066
        .await;
1✔
4067
        let token = cookie_token(&rpc_server).await;
1✔
4068
        let mut state = rpc_server.state.lock_guard_mut().await;
1✔
4069
        let peer_address0 = state.net.peer_map.values().collect::<Vec<_>>()[0].connected_address();
1✔
4070
        let peer_address1 = state.net.peer_map.values().collect::<Vec<_>>()[1].connected_address();
1✔
4071

1✔
4072
        // sanction both peers
1✔
4073
        let (standing0, standing1) = {
1✔
4074
            state.net.peer_map.entry(peer_address0).and_modify(|p| {
1✔
4075
                p.standing
1✔
4076
                    .sanction(PeerSanction::Negative(
1✔
4077
                        NegativePeerSanction::DifferentGenesis,
1✔
4078
                    ))
1✔
4079
                    .unwrap_err();
1✔
4080
            });
1✔
4081
            state.net.peer_map.entry(peer_address1).and_modify(|p| {
1✔
4082
                p.standing
1✔
4083
                    .sanction(PeerSanction::Negative(
1✔
4084
                        NegativePeerSanction::DifferentGenesis,
1✔
4085
                    ))
1✔
4086
                    .unwrap_err();
1✔
4087
            });
1✔
4088
            (
1✔
4089
                state.net.peer_map[&peer_address0].standing,
1✔
4090
                state.net.peer_map[&peer_address1].standing,
1✔
4091
            )
1✔
4092
        };
1✔
4093

1✔
4094
        state
1✔
4095
            .net
1✔
4096
            .write_peer_standing_on_decrease(peer_address0.ip(), standing0)
1✔
4097
            .await;
1✔
4098
        state
1✔
4099
            .net
1✔
4100
            .write_peer_standing_on_decrease(peer_address1.ip(), standing1)
1✔
4101
            .await;
1✔
4102

1✔
4103
        drop(state);
1✔
4104

1✔
4105
        // Verify expected initial conditions
1✔
4106
        {
1✔
4107
            let peer_standing0 = rpc_server
1✔
4108
                .state
1✔
4109
                .lock_guard_mut()
1✔
4110
                .await
1✔
4111
                .net
1✔
4112
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4113
                .await;
1✔
4114
            assert_ne!(0, peer_standing0.unwrap().standing);
1✔
4115
            assert_ne!(None, peer_standing0.unwrap().latest_punishment);
1✔
4116
        }
1✔
4117

1✔
4118
        {
1✔
4119
            let peer_standing1 = rpc_server
1✔
4120
                .state
1✔
4121
                .lock_guard_mut()
1✔
4122
                .await
1✔
4123
                .net
1✔
4124
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4125
                .await;
1✔
4126
            assert_ne!(0, peer_standing1.unwrap().standing);
1✔
4127
            assert_ne!(None, peer_standing1.unwrap().latest_punishment);
1✔
4128
        }
1✔
4129

1✔
4130
        // Verify expected reading through an RPC call
1✔
4131
        let rpc_request_context = context::current();
1✔
4132
        let after_two_sanctions = rpc_server
1✔
4133
            .clone()
1✔
4134
            .all_punished_peers(rpc_request_context, token)
1✔
4135
            .await?;
1✔
4136
        assert_eq!(2, after_two_sanctions.len());
1✔
4137

1✔
4138
        // Clear standing of both by clearing all standings
1✔
4139
        rpc_server
1✔
4140
            .clone()
1✔
4141
            .clear_all_standings(rpc_request_context, token)
1✔
4142
            .await?;
1✔
4143

1✔
4144
        let state = rpc_server.state.lock_guard().await;
1✔
4145

1✔
4146
        // Verify expected resulting conditions in database
1✔
4147
        {
1✔
4148
            let peer_standing_0 = state
1✔
4149
                .net
1✔
4150
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4151
                .await;
1✔
4152
            assert_eq!(0, peer_standing_0.unwrap().standing);
1✔
4153
            assert_eq!(None, peer_standing_0.unwrap().latest_punishment);
1✔
4154
        }
1✔
4155

1✔
4156
        {
1✔
4157
            let peer_still_standing_1 = state
1✔
4158
                .net
1✔
4159
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4160
                .await;
1✔
4161
            assert_eq!(0, peer_still_standing_1.unwrap().standing);
1✔
4162
            assert_eq!(None, peer_still_standing_1.unwrap().latest_punishment);
1✔
4163
        }
1✔
4164

1✔
4165
        // Verify expected resulting conditions in peer map
1✔
4166
        {
1✔
4167
            let peer_standing_0_from_memory = state.net.peer_map[&peer_address0].clone();
1✔
4168
            assert_eq!(0, peer_standing_0_from_memory.standing.standing);
1✔
4169
        }
1✔
4170

1✔
4171
        {
1✔
4172
            let peer_still_standing_1_from_memory = state.net.peer_map[&peer_address1].clone();
1✔
4173
            assert_eq!(0, peer_still_standing_1_from_memory.standing.standing);
1✔
4174
        }
1✔
4175

1✔
4176
        // Verify expected reading through an RPC call
1✔
4177
        let after_global_forgiveness = rpc_server
1✔
4178
            .clone()
1✔
4179
            .all_punished_peers(rpc_request_context, token)
1✔
4180
            .await?;
1✔
4181
        assert!(after_global_forgiveness.is_empty());
1✔
4182

1✔
4183
        Ok(())
1✔
4184
    }
1✔
4185

4186
    #[traced_test]
×
4187
    #[tokio::test]
4188
    async fn utxo_digest_test() {
1✔
4189
        let rpc_server = test_rpc_server(
1✔
4190
            Network::Alpha,
1✔
4191
            WalletEntropy::new_random(),
1✔
4192
            2,
1✔
4193
            cli_args::Args::default(),
1✔
4194
        )
1✔
4195
        .await;
1✔
4196
        let token = cookie_token(&rpc_server).await;
1✔
4197
        let aocl_leaves = rpc_server
1✔
4198
            .state
1✔
4199
            .lock_guard()
1✔
4200
            .await
1✔
4201
            .chain
1✔
4202
            .archival_state()
1✔
4203
            .archival_mutator_set
1✔
4204
            .ams()
1✔
4205
            .aocl
1✔
4206
            .num_leafs()
1✔
4207
            .await;
1✔
4208

1✔
4209
        debug_assert!(aocl_leaves > 0);
1✔
4210

1✔
4211
        assert!(rpc_server
1✔
4212
            .clone()
1✔
4213
            .utxo_digest(context::current(), token, aocl_leaves - 1)
1✔
4214
            .await
1✔
4215
            .unwrap()
1✔
4216
            .is_some());
1✔
4217

1✔
4218
        assert!(rpc_server
1✔
4219
            .utxo_digest(context::current(), token, aocl_leaves)
1✔
4220
            .await
1✔
4221
            .unwrap()
1✔
4222
            .is_none());
1✔
4223
    }
1✔
4224

4225
    #[traced_test]
×
4226
    #[tokio::test]
4227
    async fn block_info_test() {
1✔
4228
        let network = Network::RegTest;
1✔
4229
        let rpc_server = test_rpc_server(
1✔
4230
            network,
1✔
4231
            WalletEntropy::new_random(),
1✔
4232
            2,
1✔
4233
            cli_args::Args::default(),
1✔
4234
        )
1✔
4235
        .await;
1✔
4236
        let token = cookie_token(&rpc_server).await;
1✔
4237
        let global_state = rpc_server.state.lock_guard().await;
1✔
4238
        let ctx = context::current();
1✔
4239

1✔
4240
        let genesis_hash = global_state.chain.archival_state().genesis_block().hash();
1✔
4241
        let tip_hash = global_state.chain.light_state().hash();
1✔
4242

1✔
4243
        let genesis_block_info = BlockInfo::new(
1✔
4244
            global_state.chain.archival_state().genesis_block(),
1✔
4245
            genesis_hash,
1✔
4246
            tip_hash,
1✔
4247
            vec![],
1✔
4248
            global_state
1✔
4249
                .chain
1✔
4250
                .archival_state()
1✔
4251
                .block_belongs_to_canonical_chain(genesis_hash)
1✔
4252
                .await,
1✔
4253
        );
1✔
4254

1✔
4255
        assert!(
1✔
4256
            genesis_block_info.num_public_announcements.is_zero(),
1✔
4257
            "Genesis block contains no public announcements. Block info must reflect that."
1✔
4258
        );
1✔
4259

1✔
4260
        let tip_block_info = BlockInfo::new(
1✔
4261
            global_state.chain.light_state(),
1✔
4262
            genesis_hash,
1✔
4263
            tip_hash,
1✔
4264
            vec![],
1✔
4265
            global_state
1✔
4266
                .chain
1✔
4267
                .archival_state()
1✔
4268
                .block_belongs_to_canonical_chain(tip_hash)
1✔
4269
                .await,
1✔
4270
        );
1✔
4271

1✔
4272
        // should find genesis block by Genesis selector
1✔
4273
        assert_eq!(
1✔
4274
            genesis_block_info,
1✔
4275
            rpc_server
1✔
4276
                .clone()
1✔
4277
                .block_info(ctx, token, BlockSelector::Genesis)
1✔
4278
                .await
1✔
4279
                .unwrap()
1✔
4280
                .unwrap()
1✔
4281
        );
1✔
4282

1✔
4283
        // should find latest/tip block by Tip selector
1✔
4284
        assert_eq!(
1✔
4285
            tip_block_info,
1✔
4286
            rpc_server
1✔
4287
                .clone()
1✔
4288
                .block_info(ctx, token, BlockSelector::Tip)
1✔
4289
                .await
1✔
4290
                .unwrap()
1✔
4291
                .unwrap()
1✔
4292
        );
1✔
4293

1✔
4294
        // should find genesis block by Height selector
1✔
4295
        assert_eq!(
1✔
4296
            genesis_block_info,
1✔
4297
            rpc_server
1✔
4298
                .clone()
1✔
4299
                .block_info(ctx, token, BlockSelector::Height(BlockHeight::from(0u64)))
1✔
4300
                .await
1✔
4301
                .unwrap()
1✔
4302
                .unwrap()
1✔
4303
        );
1✔
4304

1✔
4305
        // should find genesis block by Digest selector
1✔
4306
        assert_eq!(
1✔
4307
            genesis_block_info,
1✔
4308
            rpc_server
1✔
4309
                .clone()
1✔
4310
                .block_info(ctx, token, BlockSelector::Digest(genesis_hash))
1✔
4311
                .await
1✔
4312
                .unwrap()
1✔
4313
                .unwrap()
1✔
4314
        );
1✔
4315

1✔
4316
        // should not find any block when Height selector is u64::Max
1✔
4317
        assert!(rpc_server
1✔
4318
            .clone()
1✔
4319
            .block_info(
1✔
4320
                ctx,
1✔
4321
                token,
1✔
4322
                BlockSelector::Height(BlockHeight::from(u64::MAX))
1✔
4323
            )
1✔
4324
            .await
1✔
4325
            .unwrap()
1✔
4326
            .is_none());
1✔
4327

1✔
4328
        // should not find any block when Digest selector is Digest::default()
1✔
4329
        assert!(rpc_server
1✔
4330
            .clone()
1✔
4331
            .block_info(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
4332
            .await
1✔
4333
            .unwrap()
1✔
4334
            .is_none());
1✔
4335
    }
1✔
4336

NEW
4337
    #[traced_test]
×
4338
    #[tokio::test]
4339
    async fn public_announcements_in_block_test() {
1✔
4340
        let network = Network::Main;
1✔
4341
        let mut rpc_server = test_rpc_server(
1✔
4342
            network,
1✔
4343
            WalletEntropy::new_random(),
1✔
4344
            2,
1✔
4345
            cli_args::Args::default(),
1✔
4346
        )
1✔
4347
        .await;
1✔
4348
        let mut rng = rand::rng();
1✔
4349
        let num_public_announcements_block1 = 7;
1✔
4350
        let num_inputs = 0;
1✔
4351
        let num_outputs = 2;
1✔
4352
        let tx_block1 = pseudorandom_transaction_kernel(
1✔
4353
            rng.random(),
1✔
4354
            num_inputs,
1✔
4355
            num_outputs,
1✔
4356
            num_public_announcements_block1,
1✔
4357
        );
1✔
4358
        let tx_block1 = Transaction {
1✔
4359
            kernel: tx_block1,
1✔
4360
            proof: TransactionProof::invalid(),
1✔
4361
        };
1✔
4362
        let block1 = invalid_block_with_transaction(&Block::genesis(network), tx_block1);
1✔
4363
        rpc_server.state.set_new_tip(block1.clone()).await.unwrap();
1✔
4364

1✔
4365
        let token = cookie_token(&rpc_server).await;
1✔
4366
        let ctx = context::current();
1✔
4367
        let block1_public_announcements = rpc_server
1✔
4368
            .clone()
1✔
4369
            .public_announcements_in_block(ctx, token, BlockSelector::Height(1u64.into()))
1✔
4370
            .await
1✔
4371
            .unwrap()
1✔
4372
            .unwrap();
1✔
4373
        assert_eq!(
1✔
4374
            block1.body().transaction_kernel.public_announcements,
1✔
4375
            block1_public_announcements,
1✔
4376
            "Must return expected public announcements"
1✔
4377
        );
1✔
4378
        assert_eq!(
1✔
4379
            num_public_announcements_block1,
1✔
4380
            block1_public_announcements.len(),
1✔
4381
            "Must return expected number of public announcements"
1✔
4382
        );
1✔
4383

1✔
4384
        let genesis_block_public_announcements = rpc_server
1✔
4385
            .clone()
1✔
4386
            .public_announcements_in_block(ctx, token, BlockSelector::Height(0u64.into()))
1✔
4387
            .await
1✔
4388
            .unwrap()
1✔
4389
            .unwrap();
1✔
4390
        assert!(
1✔
4391
            genesis_block_public_announcements.is_empty(),
1✔
4392
            "Genesis block has no public announements"
1✔
4393
        );
1✔
4394

1✔
4395
        assert!(
1✔
4396
            rpc_server
1✔
4397
                .public_announcements_in_block(ctx, token, BlockSelector::Height(2u64.into()))
1✔
4398
                .await
1✔
4399
                .unwrap()
1✔
4400
                .is_none(),
1✔
4401
            "Public announcements in unknown block must return None"
1✔
4402
        );
1✔
4403
    }
1✔
4404

UNCOV
4405
    #[traced_test]
×
4406
    #[tokio::test]
4407
    async fn block_digest_test() {
1✔
4408
        let network = Network::RegTest;
1✔
4409
        let rpc_server = test_rpc_server(
1✔
4410
            network,
1✔
4411
            WalletEntropy::new_random(),
1✔
4412
            2,
1✔
4413
            cli_args::Args::default(),
1✔
4414
        )
1✔
4415
        .await;
1✔
4416
        let token = cookie_token(&rpc_server).await;
1✔
4417
        let global_state = rpc_server.state.lock_guard().await;
1✔
4418
        let ctx = context::current();
1✔
4419

1✔
4420
        let genesis_hash = Block::genesis(network).hash();
1✔
4421

1✔
4422
        // should find genesis block by Genesis selector
1✔
4423
        assert_eq!(
1✔
4424
            genesis_hash,
1✔
4425
            rpc_server
1✔
4426
                .clone()
1✔
4427
                .block_digest(ctx, token, BlockSelector::Genesis)
1✔
4428
                .await
1✔
4429
                .unwrap()
1✔
4430
                .unwrap()
1✔
4431
        );
1✔
4432

1✔
4433
        // should find latest/tip block by Tip selector
1✔
4434
        assert_eq!(
1✔
4435
            global_state.chain.light_state().hash(),
1✔
4436
            rpc_server
1✔
4437
                .clone()
1✔
4438
                .block_digest(ctx, token, BlockSelector::Tip)
1✔
4439
                .await
1✔
4440
                .unwrap()
1✔
4441
                .unwrap()
1✔
4442
        );
1✔
4443

1✔
4444
        // should find genesis block by Height selector
1✔
4445
        assert_eq!(
1✔
4446
            genesis_hash,
1✔
4447
            rpc_server
1✔
4448
                .clone()
1✔
4449
                .block_digest(ctx, token, BlockSelector::Height(BlockHeight::from(0u64)))
1✔
4450
                .await
1✔
4451
                .unwrap()
1✔
4452
                .unwrap()
1✔
4453
        );
1✔
4454

1✔
4455
        // should find genesis block by Digest selector
1✔
4456
        assert_eq!(
1✔
4457
            genesis_hash,
1✔
4458
            rpc_server
1✔
4459
                .clone()
1✔
4460
                .block_digest(ctx, token, BlockSelector::Digest(genesis_hash))
1✔
4461
                .await
1✔
4462
                .unwrap()
1✔
4463
                .unwrap()
1✔
4464
        );
1✔
4465

1✔
4466
        // should not find any block when Height selector is u64::Max
1✔
4467
        assert!(rpc_server
1✔
4468
            .clone()
1✔
4469
            .block_digest(
1✔
4470
                ctx,
1✔
4471
                token,
1✔
4472
                BlockSelector::Height(BlockHeight::from(u64::MAX))
1✔
4473
            )
1✔
4474
            .await
1✔
4475
            .unwrap()
1✔
4476
            .is_none());
1✔
4477

1✔
4478
        // should not find any block when Digest selector is Digest::default()
1✔
4479
        assert!(rpc_server
1✔
4480
            .clone()
1✔
4481
            .block_digest(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
4482
            .await
1✔
4483
            .unwrap()
1✔
4484
            .is_none());
1✔
4485
    }
1✔
4486

4487
    #[traced_test]
×
4488
    #[tokio::test]
4489
    async fn getting_temperature_doesnt_crash_test() {
1✔
4490
        // On your local machine, this should return a temperature but in CI,
1✔
4491
        // the RPC call returns `None`, so we only verify that the call doesn't
1✔
4492
        // crash the host machine, we don't verify that any value is returned.
1✔
4493
        let rpc_server = test_rpc_server(
1✔
4494
            Network::Alpha,
1✔
4495
            WalletEntropy::new_random(),
1✔
4496
            2,
1✔
4497
            cli_args::Args::default(),
1✔
4498
        )
1✔
4499
        .await;
1✔
4500
        let token = cookie_token(&rpc_server).await;
1✔
4501
        let _current_server_temperature = rpc_server
1✔
4502
            .cpu_temp(context::current(), token)
1✔
4503
            .await
1✔
4504
            .unwrap();
1✔
4505
    }
1✔
4506

4507
    #[traced_test]
×
4508
    #[tokio::test]
4509
    async fn cannot_initiate_transaction_if_notx_flag_is_set() {
1✔
4510
        let network = Network::Main;
1✔
4511
        let ctx = context::current();
1✔
4512
        let mut rng = rand::rng();
1✔
4513
        let address = GenerationSpendingKey::derive_from_seed(rng.random()).to_address();
1✔
4514
        let amount = NativeCurrencyAmount::coins(rng.random_range(0..10));
1✔
4515

1✔
4516
        // set flag on, verify non-initiation
1✔
4517
        let cli_on = cli_args::Args {
1✔
4518
            no_transaction_initiation: true,
1✔
4519
            ..Default::default()
1✔
4520
        };
1✔
4521

1✔
4522
        let rpc_server = test_rpc_server(network, WalletEntropy::new_random(), 2, cli_on).await;
1✔
4523
        let token = cookie_token(&rpc_server).await;
1✔
4524

1✔
4525
        assert!(rpc_server
1✔
4526
            .clone()
1✔
4527
            .send(
1✔
4528
                ctx,
1✔
4529
                token,
1✔
4530
                amount,
1✔
4531
                address.into(),
1✔
4532
                UtxoNotificationMedium::OffChain,
1✔
4533
                UtxoNotificationMedium::OffChain,
1✔
4534
                NativeCurrencyAmount::zero()
1✔
4535
            )
1✔
4536
            .await
1✔
4537
            .is_err());
1✔
4538
        assert!(rpc_server
1✔
4539
            .clone()
1✔
4540
            .send_to_many(
1✔
4541
                ctx,
1✔
4542
                token,
1✔
4543
                vec![(address.into(), amount)],
1✔
4544
                UtxoNotificationMedium::OffChain,
1✔
4545
                UtxoNotificationMedium::OffChain,
1✔
4546
                NativeCurrencyAmount::zero()
1✔
4547
            )
1✔
4548
            .await
1✔
4549
            .is_err());
1✔
4550
    }
1✔
4551

4552
    mod pow_puzzle_tests {
4553
        use rand::random;
4554
        use tasm_lib::twenty_first::math::other::random_elements;
4555

4556
        use super::*;
4557
        use crate::mine_loop::fast_kernel_mast_hash;
4558
        use crate::models::state::block_proposal::BlockProposal;
4559
        use crate::models::state::wallet::address::hash_lock_key::HashLockKey;
4560
        use crate::tests::shared::invalid_empty_block;
4561

4562
        #[test]
4563
        fn pow_puzzle_is_consistent_with_block_hash() {
1✔
4564
            let network = Network::Main;
1✔
4565
            let genesis = Block::genesis(network);
1✔
4566
            let mut block1 = invalid_empty_block(&genesis);
1✔
4567
            let hash_lock_key = HashLockKey::from_preimage(random());
1✔
4568
            block1.set_header_guesser_digest(hash_lock_key.after_image());
1✔
4569
            let guess_challenge = ProofOfWorkPuzzle::new(block1.clone(), *genesis.header());
1✔
4570
            let nonce = random();
1✔
4571
            let resulting_block_hash = fast_kernel_mast_hash(
1✔
4572
                guess_challenge.kernel_auth_path,
1✔
4573
                guess_challenge.header_auth_path,
1✔
4574
                nonce,
1✔
4575
            );
1✔
4576

1✔
4577
            block1.set_header_nonce(nonce);
1✔
4578

1✔
4579
            assert_eq!(block1.hash(), resulting_block_hash);
1✔
4580
        }
1✔
4581

4582
        #[tokio::test]
4583
        async fn provide_solution_when_no_proposal_known() {
1✔
4584
            let network = Network::Main;
1✔
4585
            let bob = test_rpc_server(
1✔
4586
                network,
1✔
4587
                WalletEntropy::new_random(),
1✔
4588
                2,
1✔
4589
                cli_args::Args::default(),
1✔
4590
            )
1✔
4591
            .await;
1✔
4592
            let bob_token = cookie_token(&bob).await;
1✔
4593
            assert!(!bob
1✔
4594
                .state
1✔
4595
                .lock_guard()
1✔
4596
                .await
1✔
4597
                .mining_state
1✔
4598
                .block_proposal
1✔
4599
                .is_some());
1✔
4600
            let accepted = bob
1✔
4601
                .clone()
1✔
4602
                .provide_pow_solution(context::current(), bob_token, random(), random())
1✔
4603
                .await
1✔
4604
                .unwrap();
1✔
4605
            assert!(
1✔
4606
                !accepted,
1✔
4607
                "Must reject PoW solution when no proposal exists"
1✔
4608
            );
1✔
4609
        }
1✔
4610

4611
        #[tokio::test]
4612
        async fn cached_exported_proposals_are_stored_correctly() {
1✔
4613
            let network = Network::Main;
1✔
4614
            let bob = WalletEntropy::new_random();
1✔
4615
            let mut bob = test_rpc_server(network, bob.clone(), 2, cli_args::Args::default()).await;
1✔
4616

1✔
4617
            let genesis = Block::genesis(network);
1✔
4618
            let block1 = invalid_empty_block(&genesis);
1✔
4619
            bob.state
1✔
4620
                .lock_mut(|x| {
1✔
4621
                    x.mining_state.block_proposal =
1✔
4622
                        BlockProposal::ForeignComposition(block1.clone())
1✔
4623
                })
1✔
4624
                .await;
1✔
4625
            let bob_token = cookie_token(&bob).await;
1✔
4626

1✔
4627
            let num_exported_block_proposals = 6;
1✔
4628
            let guesser_digests = random_elements(6);
1✔
4629
            let mut pow_puzzle_ids = vec![];
1✔
4630
            for guesser_digest in guesser_digests.clone() {
6✔
4631
                let pow_puzzle = bob
6✔
4632
                    .clone()
6✔
4633
                    .pow_puzzle_external_key(context::current(), bob_token, guesser_digest)
6✔
4634
                    .await
6✔
4635
                    .unwrap()
6✔
4636
                    .unwrap();
6✔
4637
                assert!(!pow_puzzle_ids.contains(&pow_puzzle.id));
6✔
4638
                pow_puzzle_ids.push(pow_puzzle.id);
6✔
4639
            }
1✔
4640

1✔
4641
            assert_eq!(
1✔
4642
                num_exported_block_proposals,
1✔
4643
                bob.state
1✔
4644
                    .lock_guard()
1✔
4645
                    .await
1✔
4646
                    .mining_state
1✔
4647
                    .exported_block_proposals
1✔
4648
                    .len()
1✔
4649
            );
1✔
4650

1✔
4651
            // Verify that the same exported puzzle is not added twice.
1✔
4652
            for guesser_digest in guesser_digests {
7✔
4653
                bob.clone()
6✔
4654
                    .pow_puzzle_external_key(context::current(), bob_token, guesser_digest)
6✔
4655
                    .await
6✔
4656
                    .unwrap()
6✔
4657
                    .unwrap();
6✔
4658
            }
1✔
4659
            assert_eq!(
1✔
4660
                num_exported_block_proposals,
1✔
4661
                bob.state
1✔
4662
                    .lock_guard()
1✔
4663
                    .await
1✔
4664
                    .mining_state
1✔
4665
                    .exported_block_proposals
1✔
4666
                    .len()
1✔
4667
            );
1✔
4668
        }
1✔
4669

4670
        #[tokio::test]
4671
        async fn exported_pow_puzzle_is_consistent_with_block_hash() {
1✔
4672
            let network = Network::Main;
1✔
4673
            let bob = WalletEntropy::new_random();
1✔
4674
            let mut bob = test_rpc_server(network, bob.clone(), 2, cli_args::Args::default()).await;
1✔
4675
            let bob_token = cookie_token(&bob).await;
1✔
4676

1✔
4677
            let genesis = Block::genesis(network);
1✔
4678
            let mut block1 = invalid_empty_block(&genesis);
1✔
4679
            bob.state
1✔
4680
                .lock_mut(|x| {
1✔
4681
                    x.mining_state.block_proposal =
1✔
4682
                        BlockProposal::ForeignComposition(block1.clone())
1✔
4683
                })
1✔
4684
                .await;
1✔
4685

1✔
4686
            let external_key = WalletEntropy::new_random();
1✔
4687
            let external_guesser_key = external_key.guesser_spending_key(genesis.hash());
1✔
4688
            let external_guesser_digest = external_guesser_key.after_image();
1✔
4689
            let internal_guesser_digest = bob
1✔
4690
                .state
1✔
4691
                .lock(|x| {
1✔
4692
                    x.wallet_state
1✔
4693
                        .wallet_entropy
1✔
4694
                        .guesser_spending_key(genesis.hash())
1✔
4695
                })
1✔
4696
                .await
1✔
4697
                .after_image();
1✔
4698

1✔
4699
            for use_internal_key in [true, false] {
3✔
4700
                println!("use_internal_key: {use_internal_key}");
2✔
4701
                let pow_puzzle = if use_internal_key {
2✔
4702
                    bob.clone()
1✔
4703
                        .pow_puzzle_internal_key(context::current(), bob_token)
1✔
4704
                        .await
1✔
4705
                        .unwrap()
1✔
4706
                        .unwrap()
1✔
4707
                } else {
1✔
4708
                    bob.clone()
1✔
4709
                        .pow_puzzle_external_key(
1✔
4710
                            context::current(),
1✔
4711
                            bob_token,
1✔
4712
                            external_guesser_digest,
1✔
4713
                        )
1✔
4714
                        .await
1✔
4715
                        .unwrap()
1✔
4716
                        .unwrap()
1✔
4717
                };
1✔
4718

1✔
4719
                let guesser_digest = if use_internal_key {
2✔
4720
                    internal_guesser_digest
1✔
4721
                } else {
1✔
4722
                    external_guesser_digest
1✔
4723
                };
1✔
4724

1✔
4725
                assert!(
2✔
4726
                    bob.state
2✔
4727
                        .lock_guard()
2✔
4728
                        .await
2✔
4729
                        .mining_state
1✔
4730
                        .exported_block_proposals
1✔
4731
                        .contains_key(&pow_puzzle.id),
2✔
4732
                    "Must have stored exported block proposal"
1✔
4733
                );
1✔
4734

1✔
4735
                let mock_nonce = random();
2✔
4736
                let mut resulting_block_hash = fast_kernel_mast_hash(
2✔
4737
                    pow_puzzle.kernel_auth_path,
2✔
4738
                    pow_puzzle.header_auth_path,
2✔
4739
                    mock_nonce,
2✔
4740
                );
2✔
4741

2✔
4742
                block1.set_header_nonce(mock_nonce);
2✔
4743
                block1.set_header_guesser_digest(guesser_digest);
2✔
4744
                assert_eq!(block1.hash(), resulting_block_hash);
2✔
4745
                assert_eq!(
2✔
4746
                    block1.total_guesser_reward(),
2✔
4747
                    pow_puzzle.total_guesser_reward
2✔
4748
                );
2✔
4749

1✔
4750
                // Check that succesful guess is accepted by endpoint.
1✔
4751
                let actual_threshold = genesis.header().difficulty.target();
2✔
4752
                let mut actual_nonce = mock_nonce;
2✔
4753
                while resulting_block_hash > actual_threshold {
13,262✔
4754
                    actual_nonce = random();
13,260✔
4755
                    resulting_block_hash = fast_kernel_mast_hash(
13,260✔
4756
                        pow_puzzle.kernel_auth_path,
13,260✔
4757
                        pow_puzzle.header_auth_path,
13,260✔
4758
                        actual_nonce,
13,260✔
4759
                    );
13,260✔
4760
                }
13,260✔
4761

1✔
4762
                block1.set_header_nonce(actual_nonce);
2✔
4763
                let good_is_accepted = bob
2✔
4764
                    .clone()
2✔
4765
                    .provide_pow_solution(
2✔
4766
                        context::current(),
2✔
4767
                        bob_token,
2✔
4768
                        actual_nonce,
2✔
4769
                        pow_puzzle.id,
2✔
4770
                    )
2✔
4771
                    .await
2✔
4772
                    .unwrap();
2✔
4773
                assert!(
2✔
4774
                    good_is_accepted,
2✔
4775
                    "Actual PoW-puzzle solution must be accepted by RPC endpoint."
1✔
4776
                );
1✔
4777

1✔
4778
                // Check that bad guess is rejected by endpoint.
1✔
4779
                let mut bad_nonce: Digest = actual_nonce;
2✔
4780
                while resulting_block_hash <= actual_threshold {
4✔
4781
                    bad_nonce = random();
2✔
4782
                    resulting_block_hash = fast_kernel_mast_hash(
2✔
4783
                        pow_puzzle.kernel_auth_path,
2✔
4784
                        pow_puzzle.header_auth_path,
2✔
4785
                        bad_nonce,
2✔
4786
                    );
2✔
4787
                }
2✔
4788
                let bad_is_accepted = bob
2✔
4789
                    .clone()
2✔
4790
                    .provide_pow_solution(context::current(), bob_token, bad_nonce, pow_puzzle.id)
2✔
4791
                    .await
2✔
4792
                    .unwrap();
2✔
4793
                assert!(
2✔
4794
                    !bad_is_accepted,
2✔
4795
                    "Bad PoW solution must be rejected by RPC endpoint."
1✔
4796
                );
1✔
4797
            }
1✔
4798
        }
1✔
4799
    }
4800

4801
    mod claim_utxo_tests {
4802
        use super::*;
4803

4804
        #[traced_test]
×
4805
        #[allow(clippy::needless_return)]
4806
        #[tokio::test]
4807
        async fn claim_utxo_owned_before_confirmed() -> Result<()> {
1✔
4808
            worker::claim_utxo_owned(false, false).await
1✔
4809
        }
1✔
4810

4811
        #[traced_test]
×
4812
        #[allow(clippy::needless_return)]
4813
        #[tokio::test]
4814
        async fn claim_utxo_owned_after_confirmed() -> Result<()> {
1✔
4815
            worker::claim_utxo_owned(true, false).await
1✔
4816
        }
1✔
4817

4818
        #[traced_test]
×
4819
        #[allow(clippy::needless_return)]
4820
        #[tokio::test]
4821
        async fn claim_utxo_owned_after_confirmed_and_after_spent() -> Result<()> {
1✔
4822
            worker::claim_utxo_owned(true, true).await
1✔
4823
        }
1✔
4824

4825
        #[traced_test]
×
4826
        #[allow(clippy::needless_return)]
4827
        #[tokio::test]
4828
        async fn claim_utxo_unowned_before_confirmed() -> Result<()> {
1✔
4829
            worker::claim_utxo_unowned(false).await
1✔
4830
        }
1✔
4831

4832
        #[traced_test]
×
4833
        #[allow(clippy::needless_return)]
4834
        #[tokio::test]
4835
        async fn claim_utxo_unowned_after_confirmed() -> Result<()> {
1✔
4836
            worker::claim_utxo_unowned(true).await
1✔
4837
        }
1✔
4838

4839
        mod worker {
4840
            use cli_args::Args;
4841

4842
            use super::*;
4843
            use crate::tests::shared::invalid_block_with_transaction;
4844
            use crate::tests::shared::invalid_empty_block;
4845

4846
            pub(super) async fn claim_utxo_unowned(claim_after_confirmed: bool) -> Result<()> {
2✔
4847
                let network = Network::Main;
2✔
4848

4849
                // bob's node
4850
                let (pay_to_bob_outputs, bob_rpc_server, bob_token) = {
2✔
4851
                    let rpc_server =
2✔
4852
                        test_rpc_server(network, WalletEntropy::new_random(), 2, Args::default())
2✔
4853
                            .await;
2✔
4854
                    let token = cookie_token(&rpc_server).await;
2✔
4855

4856
                    let receiving_address_generation = rpc_server
2✔
4857
                        .clone()
2✔
4858
                        .next_receiving_address(context::current(), token, KeyType::Generation)
2✔
4859
                        .await?
2✔
4860
                        .unwrap();
2✔
4861
                    let receiving_address_symmetric = rpc_server
2✔
4862
                        .clone()
2✔
4863
                        .next_receiving_address(context::current(), token, KeyType::Symmetric)
2✔
4864
                        .await?
2✔
4865
                        .unwrap();
2✔
4866

2✔
4867
                    let pay_to_bob_outputs = vec![
2✔
4868
                        (receiving_address_generation, NativeCurrencyAmount::coins(1)),
2✔
4869
                        (receiving_address_symmetric, NativeCurrencyAmount::coins(2)),
2✔
4870
                    ];
2✔
4871

2✔
4872
                    (pay_to_bob_outputs, rpc_server, token)
2✔
4873
                };
4874

4875
                // alice's node
4876
                let (blocks, alice_to_bob_utxo_notifications, bob_amount) = {
2✔
4877
                    let wallet_entropy = WalletEntropy::new_random();
2✔
4878
                    let mut rpc_server =
2✔
4879
                        test_rpc_server(network, wallet_entropy.clone(), 2, Args::default()).await;
2✔
4880

4881
                    let genesis_block = Block::genesis(network);
2✔
4882
                    let mut blocks = vec![];
2✔
4883
                    let in_seven_months = genesis_block.header().timestamp + Timestamp::months(7);
2✔
4884

2✔
4885
                    let fee = NativeCurrencyAmount::zero();
2✔
4886
                    let bob_amount: NativeCurrencyAmount =
2✔
4887
                        pay_to_bob_outputs.iter().map(|(_, amt)| *amt).sum();
4✔
4888

2✔
4889
                    // Mine block 1 to get some coins
2✔
4890

2✔
4891
                    let cb_key = wallet_entropy.nth_generation_spending_key(0);
2✔
4892
                    let (block1, composer_expected_utxos) =
2✔
4893
                        make_mock_block(&genesis_block, None, cb_key, Default::default()).await;
2✔
4894
                    blocks.push(block1.clone());
2✔
4895

2✔
4896
                    rpc_server
2✔
4897
                        .state
2✔
4898
                        .set_new_self_composed_tip(block1.clone(), composer_expected_utxos)
2✔
4899
                        .await
2✔
4900
                        .unwrap();
2✔
4901

4902
                    let (tx, offchain_notifications) = rpc_server
2✔
4903
                        .clone()
2✔
4904
                        .send_to_many_inner_invalid_proof(
2✔
4905
                            pay_to_bob_outputs,
2✔
4906
                            UtxoNotificationMedium::OffChain,
2✔
4907
                            UtxoNotificationMedium::OffChain,
2✔
4908
                            fee,
2✔
4909
                            in_seven_months,
2✔
4910
                        )
2✔
4911
                        .await
2✔
4912
                        .unwrap();
2✔
4913

2✔
4914
                    let block2 = invalid_block_with_transaction(&block1, tx);
2✔
4915
                    let block3 = invalid_empty_block(&block2);
2✔
4916

2✔
4917
                    // mine two blocks, the first will include the transaction
2✔
4918
                    blocks.push(block2);
2✔
4919
                    blocks.push(block3);
2✔
4920

2✔
4921
                    (blocks, offchain_notifications, bob_amount)
2✔
4922
                };
2✔
4923

2✔
4924
                // bob's node claims each utxo
2✔
4925
                {
2✔
4926
                    let mut state = bob_rpc_server.state.clone();
2✔
4927

2✔
4928
                    state.set_new_tip(blocks[0].clone()).await?;
2✔
4929

4930
                    if claim_after_confirmed {
2✔
4931
                        state.set_new_tip(blocks[1].clone()).await?;
1✔
4932
                        state.set_new_tip(blocks[2].clone()).await?;
1✔
4933
                    }
1✔
4934

4935
                    for utxo_notification in alice_to_bob_utxo_notifications {
6✔
4936
                        // Register the same UTXO multiple times to ensure that this does not
4937
                        // change the balance.
4938
                        let claim_was_new0 = bob_rpc_server
4✔
4939
                            .clone()
4✔
4940
                            .claim_utxo(
4✔
4941
                                context::current(),
4✔
4942
                                bob_token,
4✔
4943
                                utxo_notification.ciphertext.clone(),
4✔
4944
                                None,
4✔
4945
                            )
4✔
4946
                            .await
4✔
4947
                            .unwrap();
4✔
4948
                        assert!(claim_was_new0);
4✔
4949
                        let claim_was_new1 = bob_rpc_server
4✔
4950
                            .clone()
4✔
4951
                            .claim_utxo(
4✔
4952
                                context::current(),
4✔
4953
                                bob_token,
4✔
4954
                                utxo_notification.ciphertext,
4✔
4955
                                None,
4✔
4956
                            )
4✔
4957
                            .await
4✔
4958
                            .unwrap();
4✔
4959
                        assert!(!claim_was_new1);
4✔
4960
                    }
4961

4962
                    assert_eq!(
2✔
4963
                        vec![
2✔
4964
                            NativeCurrencyAmount::coins(1), // claimed via generation addr
2✔
4965
                            NativeCurrencyAmount::coins(2), // claimed via symmetric addr
2✔
4966
                        ],
2✔
4967
                        state
2✔
4968
                            .lock_guard()
2✔
4969
                            .await
2✔
4970
                            .wallet_state
4971
                            .wallet_db
4972
                            .expected_utxos()
2✔
4973
                            .get_all()
2✔
4974
                            .await
2✔
4975
                            .iter()
2✔
4976
                            .map(|eu| eu.utxo.get_native_currency_amount())
4✔
4977
                            .collect_vec()
2✔
4978
                    );
4979

4980
                    if !claim_after_confirmed {
2✔
4981
                        assert_eq!(
1✔
4982
                            NativeCurrencyAmount::zero(),
1✔
4983
                            bob_rpc_server
1✔
4984
                                .clone()
1✔
4985
                                .confirmed_available_balance(context::current(), bob_token)
1✔
4986
                                .await?,
1✔
4987
                        );
4988
                        state.set_new_tip(blocks[1].clone()).await?;
1✔
4989
                        state.set_new_tip(blocks[2].clone()).await?;
1✔
4990
                    }
1✔
4991

4992
                    assert_eq!(
2✔
4993
                        bob_amount,
2✔
4994
                        bob_rpc_server
2✔
4995
                            .confirmed_available_balance(context::current(), bob_token)
2✔
4996
                            .await?,
2✔
4997
                    );
4998
                }
4999

5000
                Ok(())
2✔
5001
            }
2✔
5002

5003
            pub(super) async fn claim_utxo_owned(
3✔
5004
                claim_after_mined: bool,
3✔
5005
                spent: bool,
3✔
5006
            ) -> Result<()> {
3✔
5007
                assert!(
3✔
5008
                    !spent || claim_after_mined,
3✔
5009
                    "If UTXO is spent, it must also be mined"
×
5010
                );
5011
                let network = Network::Main;
3✔
5012
                let bob_wallet = WalletEntropy::new_random();
3✔
5013
                let mut bob =
3✔
5014
                    test_rpc_server(network, bob_wallet.clone(), 2, Args::default()).await;
3✔
5015
                let bob_token = cookie_token(&bob).await;
3✔
5016

5017
                let in_seven_months =
3✔
5018
                    Block::genesis(network).header().timestamp + Timestamp::months(7);
3✔
5019
                let in_eight_months = in_seven_months + Timestamp::months(1);
3✔
5020

3✔
5021
                let bob_key = bob_wallet.nth_generation_spending_key(0);
3✔
5022
                let genesis_block = Block::genesis(network);
3✔
5023
                let (block1, composer_expected_utxos) =
3✔
5024
                    make_mock_block(&genesis_block, None, bob_key, Default::default()).await;
3✔
5025

5026
                bob.state
3✔
5027
                    .set_new_self_composed_tip(block1.clone(), composer_expected_utxos)
3✔
5028
                    .await
3✔
5029
                    .unwrap();
3✔
5030

5031
                let bob_gen_addr = bob
3✔
5032
                    .clone()
3✔
5033
                    .next_receiving_address(context::current(), bob_token, KeyType::Generation)
3✔
5034
                    .await?
3✔
5035
                    .unwrap();
3✔
5036
                let bob_sym_addr = bob
3✔
5037
                    .clone()
3✔
5038
                    .next_receiving_address(context::current(), bob_token, KeyType::Symmetric)
3✔
5039
                    .await?
3✔
5040
                    .unwrap();
3✔
5041

3✔
5042
                let pay_to_self_outputs = vec![
3✔
5043
                    (bob_gen_addr, NativeCurrencyAmount::coins(5)),
3✔
5044
                    (bob_sym_addr, NativeCurrencyAmount::coins(6)),
3✔
5045
                ];
3✔
5046

3✔
5047
                let fee = NativeCurrencyAmount::coins(2);
3✔
5048
                let (tx, offchain_notifications) = bob
3✔
5049
                    .clone()
3✔
5050
                    .send_to_many_inner_invalid_proof(
3✔
5051
                        pay_to_self_outputs.clone(),
3✔
5052
                        UtxoNotificationMedium::OffChain,
3✔
5053
                        UtxoNotificationMedium::OffChain,
3✔
5054
                        fee,
3✔
5055
                        in_eight_months,
3✔
5056
                    )
3✔
5057
                    .await
3✔
5058
                    .unwrap();
3✔
5059

3✔
5060
                // alice mines 2 more blocks.  block2 confirms the sent tx.
3✔
5061
                let block2 = invalid_block_with_transaction(&block1, tx);
3✔
5062
                let block3 = invalid_empty_block(&block2);
3✔
5063

3✔
5064
                if claim_after_mined {
3✔
5065
                    // bob applies the blocks before claiming utxos.
5066
                    bob.state.set_new_tip(block2.clone()).await?;
2✔
5067
                    bob.state.set_new_tip(block3.clone()).await?;
2✔
5068

5069
                    if spent {
2✔
5070
                        // Send entire liquid balance somewhere else
5071
                        let another_address = WalletEntropy::new_random()
1✔
5072
                            .nth_generation_spending_key(0)
1✔
5073
                            .to_address();
1✔
5074
                        let (spending_tx, _) = bob
1✔
5075
                            .clone()
1✔
5076
                            .send_to_many_inner_invalid_proof(
1✔
5077
                                vec![(another_address.into(), NativeCurrencyAmount::coins(62))],
1✔
5078
                                UtxoNotificationMedium::OffChain,
1✔
5079
                                UtxoNotificationMedium::OffChain,
1✔
5080
                                NativeCurrencyAmount::zero(),
1✔
5081
                                in_eight_months,
1✔
5082
                            )
1✔
5083
                            .await
1✔
5084
                            .unwrap();
1✔
5085
                        let block4 = invalid_block_with_transaction(&block3, spending_tx);
1✔
5086
                        bob.state.set_new_tip(block4.clone()).await?;
1✔
5087
                    }
1✔
5088
                }
1✔
5089

5090
                for offchain_notification in offchain_notifications {
9✔
5091
                    bob.clone()
6✔
5092
                        .claim_utxo(
6✔
5093
                            context::current(),
6✔
5094
                            bob_token,
6✔
5095
                            offchain_notification.ciphertext,
6✔
5096
                            None,
6✔
5097
                        )
6✔
5098
                        .await?;
6✔
5099
                }
5100

5101
                assert_eq!(
3✔
5102
                    vec![
3✔
5103
                        NativeCurrencyAmount::coins(64), // liquid composer reward, block 1
3✔
5104
                        NativeCurrencyAmount::coins(64), // illiquid composer reward, block 1
3✔
5105
                        NativeCurrencyAmount::coins(5),  // claimed via generation addr
3✔
5106
                        NativeCurrencyAmount::coins(6),  // claimed via symmetric addr
3✔
5107
                        // 51 = (64 - 5 - 6 - 2 (fee))
3✔
5108
                        NativeCurrencyAmount::coins(51) // change (symmetric addr)
3✔
5109
                    ],
3✔
5110
                    bob.state
3✔
5111
                        .lock_guard()
3✔
5112
                        .await
3✔
5113
                        .wallet_state
5114
                        .wallet_db
5115
                        .expected_utxos()
3✔
5116
                        .get_all()
3✔
5117
                        .await
3✔
5118
                        .iter()
3✔
5119
                        .map(|eu| eu.utxo.get_native_currency_amount())
15✔
5120
                        .collect_vec()
3✔
5121
                );
5122

5123
                if !claim_after_mined {
3✔
5124
                    // bob hasn't applied blocks 2,3. liquid balance should be 64
5125
                    assert_eq!(
1✔
5126
                        NativeCurrencyAmount::coins(64),
1✔
5127
                        bob.clone()
1✔
5128
                            .confirmed_available_balance(context::current(), bob_token)
1✔
5129
                            .await?,
1✔
5130
                    );
5131
                    // bob applies the blocks after claiming utxos.
5132
                    bob.state.set_new_tip(block2).await?;
1✔
5133
                    bob.state.set_new_tip(block3).await?;
1✔
5134
                }
2✔
5135

5136
                if spent {
3✔
5137
                    assert!(bob
1✔
5138
                        .confirmed_available_balance(context::current(), bob_token)
1✔
5139
                        .await?
1✔
5140
                        .is_zero(),);
1✔
5141
                } else {
5142
                    // final liquid balance should be 62.
5143
                    // +64 composer liquid
5144
                    // +64 composer timelocked (not counted)
5145
                    // -64 composer liquid spent
5146
                    // +5 self-send via Generation
5147
                    // +6 self-send via Symmetric
5148
                    // +51   change (less fee == 2)
5149
                    assert_eq!(
2✔
5150
                        NativeCurrencyAmount::coins(62),
2✔
5151
                        bob.confirmed_available_balance(context::current(), bob_token)
2✔
5152
                            .await?,
2✔
5153
                    );
5154
                }
5155
                Ok(())
3✔
5156
            }
3✔
5157
        }
5158
    }
5159

5160
    mod send_tests {
5161
        use super::*;
5162

5163
        #[traced_test]
×
5164
        #[tokio::test]
5165
        async fn send_to_many_n_outputs() {
1✔
5166
            let mut rng = StdRng::seed_from_u64(1815);
1✔
5167
            let network = Network::Main;
1✔
5168
            let rpc_server = test_rpc_server(
1✔
5169
                network,
1✔
5170
                WalletEntropy::new_pseudorandom(rng.random()),
1✔
5171
                2,
1✔
5172
                cli_args::Args::default(),
1✔
5173
            )
1✔
5174
            .await;
1✔
5175
            let token = cookie_token(&rpc_server).await;
1✔
5176

1✔
5177
            let ctx = context::current();
1✔
5178
            let timestamp = network.launch_date() + Timestamp::days(1);
1✔
5179
            let own_address = rpc_server
1✔
5180
                .clone()
1✔
5181
                .next_receiving_address(ctx, token, KeyType::Generation)
1✔
5182
                .await
1✔
5183
                .unwrap()
1✔
5184
                .unwrap();
1✔
5185
            let elem = (own_address.clone(), NativeCurrencyAmount::zero());
1✔
5186
            let outputs = std::iter::repeat(elem);
1✔
5187
            let fee = NativeCurrencyAmount::zero();
1✔
5188

1✔
5189
            // note: we can only perform 2 iters, else we bump into send rate-limit (per block)
1✔
5190
            for i in 5..7 {
3✔
5191
                let result = rpc_server
2✔
5192
                    .clone()
2✔
5193
                    .send_to_many_inner(
2✔
5194
                        ctx,
2✔
5195
                        outputs.clone().take(i).collect(),
2✔
5196
                        (
2✔
5197
                            UtxoNotificationMedium::OffChain,
2✔
5198
                            UtxoNotificationMedium::OffChain,
2✔
5199
                        ),
2✔
5200
                        fee,
2✔
5201
                        timestamp,
2✔
5202
                        TxProvingCapability::PrimitiveWitness,
2✔
5203
                    )
2✔
5204
                    .await;
2✔
5205
                assert!(result.is_ok());
2✔
5206
            }
1✔
5207
        }
1✔
5208

5209
        /// sends a tx with two outputs: one self, one external, for each key type
5210
        /// that accepts incoming UTXOs.
5211
        #[traced_test]
×
5212
        #[tokio::test]
5213
        #[allow(clippy::needless_return)]
5214
        async fn send_to_many_test() -> Result<()> {
1✔
5215
            for recipient_key_type in KeyType::all_types_for_receiving() {
3✔
5216
                worker::send_to_many(recipient_key_type).await?;
2✔
5217
            }
1✔
5218
            Ok(())
1✔
5219
        }
1✔
5220

5221
        /// checks that the sending rate limit kicks in after 2 tx are sent.
5222
        /// note: rate-limit only applies below block 25000
5223
        #[traced_test]
×
5224
        #[tokio::test]
5225
        #[allow(clippy::needless_return)]
5226
        async fn send_rate_limit() -> Result<()> {
1✔
5227
            let mut rng = StdRng::seed_from_u64(1815);
1✔
5228
            let network = Network::Main;
1✔
5229
            let rpc_server = test_rpc_server(
1✔
5230
                network,
1✔
5231
                WalletEntropy::devnet_wallet(),
1✔
5232
                2,
1✔
5233
                cli_args::Args::default(),
1✔
5234
            )
1✔
5235
            .await;
1✔
5236

1✔
5237
            let ctx = context::current();
1✔
5238
            let timestamp = network.launch_date() + Timestamp::months(7);
1✔
5239

1✔
5240
            let address: ReceivingAddress = GenerationSpendingKey::derive_from_seed(rng.random())
1✔
5241
                .to_address()
1✔
5242
                .into();
1✔
5243
            let amount = NativeCurrencyAmount::coins(rng.random_range(0..10));
1✔
5244
            let fee = NativeCurrencyAmount::coins(1);
1✔
5245

1✔
5246
            let outputs = vec![(address, amount)];
1✔
5247

1✔
5248
            for i in 0..10 {
11✔
5249
                let result = rpc_server
10✔
5250
                    .clone()
10✔
5251
                    .send_to_many_inner(
10✔
5252
                        ctx,
10✔
5253
                        outputs.clone(),
10✔
5254
                        (
10✔
5255
                            UtxoNotificationMedium::OnChain,
10✔
5256
                            UtxoNotificationMedium::OnChain,
10✔
5257
                        ),
10✔
5258
                        fee,
10✔
5259
                        timestamp,
10✔
5260
                        TxProvingCapability::PrimitiveWitness,
10✔
5261
                    )
10✔
5262
                    .await;
10✔
5263

1✔
5264
                // any attempts after the 2nd send should result in RateLimit error.
1✔
5265
                match i {
10✔
5266
                    0..2 => assert!(result.is_ok()),
10✔
5267
                    _ => assert!(matches!(result, Err(error::SendError::RateLimit { .. }))),
8✔
5268
                }
1✔
5269
            }
1✔
5270

1✔
5271
            Ok(())
1✔
5272
        }
1✔
5273

5274
        mod worker {
5275
            use super::*;
5276
            use crate::models::state::wallet::address::generation_address::GenerationReceivingAddress;
5277
            use crate::models::state::wallet::address::symmetric_key::SymmetricKey;
5278

5279
            // sends a tx with two outputs: one self, one external.
5280
            //
5281
            // input: recipient_key_type: can be symmetric or generation.
5282
            //
5283
            // Steps:
5284
            // --- Init.  Basics ---
5285
            // --- Init.  get wallet spending key ---
5286
            // --- Init.  generate a block, with coinbase going to our wallet ---
5287
            // --- Init.  append the block to blockchain ---
5288
            // --- Setup. generate an output that our wallet cannot claim. ---
5289
            // --- Setup. generate an output that our wallet can claim. ---
5290
            // --- Setup. assemble outputs and fee ---
5291
            // --- Store: store num expected utxo before spend ---
5292
            // --- Operation: perform send_to_many
5293
            // --- Test: bech32m serialize/deserialize roundtrip.
5294
            // --- Test: verify op returns a value.
5295
            // --- Test: verify expected_utxos.len() has increased by 2.
5296
            pub(super) async fn send_to_many(recipient_key_type: KeyType) -> Result<()> {
2✔
5297
                info!("recipient_key_type: {}", recipient_key_type);
2✔
5298

5299
                // --- Init.  Basics ---
5300
                let mut rng = StdRng::seed_from_u64(1814);
2✔
5301
                let network = Network::Main;
2✔
5302
                let mut rpc_server = test_rpc_server(
2✔
5303
                    network,
2✔
5304
                    WalletEntropy::new_pseudorandom(rng.random()),
2✔
5305
                    2,
2✔
5306
                    cli_args::Args::default(),
2✔
5307
                )
2✔
5308
                .await;
2✔
5309
                let ctx = context::current();
2✔
5310

2✔
5311
                // --- Init.  get wallet spending key ---
2✔
5312
                let genesis_block = Block::genesis(network);
2✔
5313
                let wallet_spending_key = rpc_server
2✔
5314
                    .state
2✔
5315
                    .lock_guard_mut()
2✔
5316
                    .await
2✔
5317
                    .wallet_state
5318
                    .next_unused_spending_key(KeyType::Generation)
2✔
5319
                    .await
2✔
5320
                    .unwrap();
2✔
5321
                let SpendingKey::Generation(wallet_spending_key) = wallet_spending_key else {
2✔
5322
                    panic!("Expected generation key")
×
5323
                };
5324

5325
                // --- Init.  generate a block, with composer fee going to our
5326
                // wallet ---
5327
                let timestamp = network.launch_date() + Timestamp::days(1);
2✔
5328
                let (block_1, composer_utxos) = make_mock_block(
2✔
5329
                    &genesis_block,
2✔
5330
                    Some(timestamp),
2✔
5331
                    wallet_spending_key,
2✔
5332
                    rng.random(),
2✔
5333
                )
2✔
5334
                .await;
2✔
5335

5336
                {
5337
                    let state_lock = rpc_server.state.lock_guard().await;
2✔
5338
                    let wallet_status = state_lock.get_wallet_status_for_tip().await;
2✔
5339
                    let original_balance = state_lock
2✔
5340
                        .wallet_state
2✔
5341
                        .confirmed_available_balance(&wallet_status, timestamp);
2✔
5342
                    assert!(original_balance.is_zero(), "Original balance assumed zero");
2✔
5343
                };
5344

5345
                // --- Init.  append the block to blockchain ---
5346
                rpc_server
2✔
5347
                    .state
2✔
5348
                    .set_new_self_composed_tip(block_1.clone(), composer_utxos)
2✔
5349
                    .await?;
2✔
5350

5351
                {
5352
                    let state_lock = rpc_server.state.lock_guard().await;
2✔
5353
                    let wallet_status = state_lock.get_wallet_status_for_tip().await;
2✔
5354
                    let new_balance = state_lock
2✔
5355
                        .wallet_state
2✔
5356
                        .confirmed_available_balance(&wallet_status, timestamp);
2✔
5357
                    let mut expected_balance = Block::block_subsidy(block_1.header().height);
2✔
5358
                    expected_balance.div_two();
2✔
5359
                    assert_eq!(
2✔
5360
                        expected_balance, new_balance,
5361
                        "New balance must be exactly 1/2 mining reward bc timelock"
×
5362
                    );
5363
                };
5364

5365
                // --- Setup. generate an output that our wallet cannot claim. ---
5366
                let external_receiving_address: ReceivingAddress = match recipient_key_type {
2✔
5367
                    KeyType::Generation => {
5368
                        GenerationReceivingAddress::derive_from_seed(rng.random()).into()
1✔
5369
                    }
5370
                    KeyType::Symmetric => SymmetricKey::from_seed(rng.random()).into(),
1✔
5371
                    KeyType::RawHashLock => panic!("hash lock key not supported"),
×
5372
                };
5373
                let output1 = (
2✔
5374
                    external_receiving_address.clone(),
2✔
5375
                    NativeCurrencyAmount::coins(5),
2✔
5376
                );
2✔
5377

5378
                // --- Setup. generate an output that our wallet can claim. ---
5379
                let output2 = {
2✔
5380
                    let spending_key = rpc_server
2✔
5381
                        .state
2✔
5382
                        .lock_guard_mut()
2✔
5383
                        .await
2✔
5384
                        .wallet_state
5385
                        .next_unused_spending_key(recipient_key_type)
2✔
5386
                        .await
2✔
5387
                        .unwrap();
2✔
5388
                    (
2✔
5389
                        spending_key.to_address().unwrap(),
2✔
5390
                        NativeCurrencyAmount::coins(25),
2✔
5391
                    )
2✔
5392
                };
2✔
5393

2✔
5394
                // --- Setup. assemble outputs and fee ---
2✔
5395
                let outputs = vec![output1, output2];
2✔
5396
                let fee = NativeCurrencyAmount::coins(1);
2✔
5397

5398
                // --- Store: store num expected utxo before spend ---
5399
                let num_expected_utxo = rpc_server
2✔
5400
                    .state
2✔
5401
                    .lock_guard()
2✔
5402
                    .await
2✔
5403
                    .wallet_state
5404
                    .wallet_db
5405
                    .expected_utxos()
2✔
5406
                    .len()
2✔
5407
                    .await;
2✔
5408

5409
                // --- Operation: perform send_to_many
5410
                // It's important to call a method where you get to inject the
5411
                // timestamp. Otherwise, proofs cannot be reused, and CI will
5412
                // fail. CI might also fail if you don't set an explicit proving
5413
                // capability.
5414
                let result = rpc_server
2✔
5415
                    .clone()
2✔
5416
                    .send_to_many_inner(
2✔
5417
                        ctx,
2✔
5418
                        outputs,
2✔
5419
                        (
2✔
5420
                            UtxoNotificationMedium::OffChain,
2✔
5421
                            UtxoNotificationMedium::OffChain,
2✔
5422
                        ),
2✔
5423
                        fee,
2✔
5424
                        timestamp,
2✔
5425
                        TxProvingCapability::ProofCollection,
2✔
5426
                    )
2✔
5427
                    .await;
2✔
5428

5429
                // --- Test: bech32m serialize/deserialize roundtrip.
5430
                assert_eq!(
2✔
5431
                    external_receiving_address,
2✔
5432
                    ReceivingAddress::from_bech32m(
2✔
5433
                        &external_receiving_address.to_bech32m(network)?,
2✔
5434
                        network,
2✔
5435
                    )?
×
5436
                );
5437

5438
                // --- Test: verify op returns a value.
5439
                assert!(result.is_ok());
2✔
5440

5441
                // --- Test: verify expected_utxos.len() has increased by 2.
5442
                //           (one off-chain utxo + one change utxo)
5443
                assert_eq!(
2✔
5444
                    rpc_server
2✔
5445
                        .state
2✔
5446
                        .lock_guard()
2✔
5447
                        .await
2✔
5448
                        .wallet_state
5449
                        .wallet_db
5450
                        .expected_utxos()
2✔
5451
                        .len()
2✔
5452
                        .await,
2✔
5453
                    num_expected_utxo + 2
2✔
5454
                );
5455

5456
                Ok(())
2✔
5457
            }
2✔
5458
        }
5459
    }
5460
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc