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

Neptune-Crypto / neptune-core / 14082870795

26 Mar 2025 12:07PM UTC coverage: 84.277% (-0.02%) from 84.3%
14082870795

push

github

Sword-Smith
feat(rpc_server): Add command to clear mempool

0 of 24 new or added lines in 3 files covered. (0.0%)

1 existing line in 1 file now uncovered.

50969 of 60478 relevant lines covered (84.28%)

175125.71 hits per line

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

89.67
/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
    // All fields public since used downstream by mining pool software.
213
    pub kernel_auth_path: [Digest; BlockKernel::MAST_HEIGHT],
214
    pub header_auth_path: [Digest; BlockHeader::MAST_HEIGHT],
215

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

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

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

228
    /// Indicates whether template is invalid due to the presence of a new tip.
229
    /// Can be used to reset templates in pools that perform local checks before
230
    /// submitting a solution to the node.
231
    pub prev_block: Digest,
232
}
233

234
impl ProofOfWorkPuzzle {
235
    /// Return a PoW puzzle assuming that the caller has already set the correct
236
    /// guesser digest.
237
    fn new(block_proposal: Block, latest_block_header: BlockHeader) -> Self {
15✔
238
        let guesser_reward = block_proposal.total_guesser_reward();
15✔
239
        let (kernel_auth_path, header_auth_path) = precalculate_block_auth_paths(&block_proposal);
15✔
240
        let threshold = latest_block_header.difficulty.target();
15✔
241
        let prev_block = block_proposal.header().prev_block_digest;
15✔
242

15✔
243
        let id = Tip5::hash(&(kernel_auth_path, header_auth_path));
15✔
244

15✔
245
        Self {
15✔
246
            kernel_auth_path,
15✔
247
            header_auth_path,
15✔
248
            threshold,
15✔
249
            total_guesser_reward: guesser_reward,
15✔
250
            id,
15✔
251
            prev_block,
15✔
252
        }
15✔
253
    }
15✔
254
}
255

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

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

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

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

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

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

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

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

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

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

606
    /// Return the public announements contained in a specified block.
607
    ///
608
    /// Returns `None` if the selected block could not be found, otherwise
609
    /// returns `Some(public_announcements)`.
610
    ///
611
    /// Does not attempt to decode the public announcements.
612
    async fn public_announcements_in_block(
613
        token: rpc_auth::Token,
614
        block_selector: BlockSelector,
615
    ) -> RpcResult<Option<Vec<PublicAnnouncement>>>;
616

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1362
    /// Get the proof-of-work puzzle for the current block proposal. Uses the
1363
    /// node's secret key to populate the guesser digest.
1364
    ///
1365
    /// Returns `None` if no block proposal for the next block is known yet.
1366
    async fn pow_puzzle_internal_key(
1367
        token: rpc_auth::Token,
1368
    ) -> RpcResult<Option<ProofOfWorkPuzzle>>;
1369

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

1383
    /******** BLOCKCHAIN STATISTICS ********/
1384
    // Place all endpoints that relate to statistics of the blockchain here
1385

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

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

1441
    /******** PEER INTERACTIONS ********/
1442

1443
    /// Broadcast transaction notifications for all transactions in this node's
1444
    /// mempool.
1445
    async fn broadcast_all_mempool_txs(token: rpc_auth::Token) -> RpcResult<()>;
1446

1447
    /******** CHANGE THINGS ********/
1448
    // Place all things that change state here
1449

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

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

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

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

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

1725
    /// Delete all transactions from the mempool.
1726
    async fn clear_mempool(token: rpc_auth::Token) -> RpcResult<()>;
1727

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

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

1794
    /// Provide a PoW-solution to the current block proposal.
1795
    ///
1796
    /// If the solution is considered valid by the running node, the new block
1797
    /// is broadcast to all peers on the network, and `true` is returned.
1798
    /// Otherwise the provided solution is ignored, and `false` is returned.
1799
    async fn provide_pow_solution(
1800
        token: rpc_auth::Token,
1801
        nonce: Digest,
1802
        proposal_id: Digest,
1803
    ) -> RpcResult<bool>;
1804

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

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

1871
#[derive(Clone)]
1872
pub(crate) struct NeptuneRPCServer {
1873
    pub(crate) state: GlobalStateLock,
1874
    pub(crate) rpc_server_to_main_tx: tokio::sync::mpsc::Sender<RPCServerToMain>,
1875

1876
    // copy of DataDirectory for this neptune-core instance.
1877
    data_directory: DataDirectory,
1878

1879
    // list of tokens that are valid.  RPC clients must present a token that
1880
    // matches one of these.  there should only be one of each `Token` variant
1881
    // in the list (dups ignored).
1882
    valid_tokens: Vec<rpc_auth::Token>,
1883
}
1884

1885
impl NeptuneRPCServer {
1886
    /// instantiate a new [NeptuneRPCServer]
1887
    pub fn new(
29✔
1888
        state: GlobalStateLock,
29✔
1889
        rpc_server_to_main_tx: tokio::sync::mpsc::Sender<RPCServerToMain>,
29✔
1890
        data_directory: DataDirectory,
29✔
1891
        valid_tokens: Vec<rpc_auth::Token>,
29✔
1892
    ) -> Self {
29✔
1893
        Self {
29✔
1894
            state,
29✔
1895
            valid_tokens,
29✔
1896
            rpc_server_to_main_tx,
29✔
1897
            data_directory,
29✔
1898
        }
29✔
1899
    }
29✔
1900

1901
    async fn confirmations_internal(&self, state: &GlobalState) -> Option<BlockHeight> {
1✔
1902
        match state.get_latest_balance_height().await {
1✔
1903
            Some(latest_balance_height) => {
×
1904
                let tip_block_header = state.chain.light_state().header();
×
1905

×
1906
                assert!(tip_block_header.height >= latest_balance_height);
×
1907

1908
                // subtract latest balance height from chain tip.
1909
                //
1910
                // we add 1 to the result because the block that a tx is confirmed
1911
                // in is considered the 1st confirmation.
1912
                //
1913
                // note: BlockHeight is u64 internally and BlockHeight::sub() returns i128.
1914
                //       The subtraction and cast is safe given we passed the above assert.
1915
                let confirmations: BlockHeight =
×
1916
                    ((tip_block_header.height - latest_balance_height) as u64 + 1).into();
×
1917
                Some(confirmations)
×
1918
            }
1919
            None => None,
1✔
1920
        }
1921
    }
1✔
1922

1923
    /// Return temperature of CPU, if available.
1924
    fn cpu_temp_inner() -> Option<f32> {
1✔
1925
        let current_system = System::new();
1✔
1926
        current_system.cpu_temp().ok()
1✔
1927
    }
1✔
1928

1929
    async fn send_to_many_inner_with_mock_proof_option(
21✔
1930
        mut self,
21✔
1931
        outputs: Vec<(ReceivingAddress, NativeCurrencyAmount)>,
21✔
1932
        utxo_notification_media: (UtxoNotificationMedium, UtxoNotificationMedium),
21✔
1933
        fee: NativeCurrencyAmount,
21✔
1934
        now: Timestamp,
21✔
1935
        tx_proving_capability: TxProvingCapability,
21✔
1936
        mocked_invalid_proof: Option<TransactionProof>,
21✔
1937
    ) -> Result<(Transaction, Vec<PrivateNotificationData>), error::SendError> {
21✔
1938
        let (owned_utxo_notification_medium, unowned_utxo_notification_medium) =
21✔
1939
            utxo_notification_media;
21✔
1940

1941
        // check if this send would exceed the send rate-limit (per block)
1942
        {
1943
            // send rate limiting only applies below height 25000
1944
            // which is approx 5.6 months after launch.
1945
            // after that, the training wheel come off.
1946
            const RATE_LIMIT_UNTIL_HEIGHT: u64 = 25000;
1947
            let state = self.state.lock_guard().await;
21✔
1948

1949
            if state.chain.light_state().header().height < RATE_LIMIT_UNTIL_HEIGHT.into() {
21✔
1950
                const RATE_LIMIT: usize = 2;
1951
                let tip_digest = state.chain.light_state().hash();
21✔
1952
                let send_count_at_tip = state
21✔
1953
                    .wallet_state
21✔
1954
                    .count_sent_transactions_at_block(tip_digest)
21✔
1955
                    .await;
21✔
1956
                tracing::debug!(
21✔
1957
                    "send-tx rate-limit check:  found {} sent-tx at current tip.  limit = {}",
×
1958
                    send_count_at_tip,
1959
                    RATE_LIMIT
1960
                );
1961
                if send_count_at_tip >= RATE_LIMIT {
21✔
1962
                    let height = state.chain.light_state().header().height;
8✔
1963
                    let e = error::SendError::RateLimit {
8✔
1964
                        height,
8✔
1965
                        tip_digest,
8✔
1966
                        max: RATE_LIMIT,
8✔
1967
                    };
8✔
1968
                    tracing::warn!("{}", e.to_string());
8✔
1969
                    return Err(e);
8✔
1970
                }
13✔
1971
            }
×
1972
        }
1973

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

1976
        // obtain next unused symmetric key for change utxo
1977
        let change_key = {
13✔
1978
            let mut s = self.state.lock_guard_mut().await;
13✔
1979
            let key = s
13✔
1980
                .wallet_state
13✔
1981
                .next_unused_spending_key(KeyType::Symmetric)
13✔
1982
                .await
13✔
1983
                .expect("wallet should be capable of generating symmetric spending keys");
13✔
1984

13✔
1985
            // write state to disk. create_transaction() may be slow.
13✔
1986
            s.persist_wallet().await.expect("flushed");
13✔
1987
            key
13✔
1988
        };
13✔
1989

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

1992
        let state = self.state.lock_guard().await;
13✔
1993
        let tx_outputs = state.generate_tx_outputs(
13✔
1994
            outputs,
13✔
1995
            owned_utxo_notification_medium,
13✔
1996
            unowned_utxo_notification_medium,
13✔
1997
        );
13✔
1998

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

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

2029
        if let Some(invalid_proof) = mocked_invalid_proof {
12✔
2030
            transaction.proof = invalid_proof;
6✔
2031
        }
6✔
2032

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

2035
        let offchain_notifications = tx_outputs.private_notifications(self.state.cli().network);
12✔
2036
        tracing::debug!(
12✔
2037
            "Generated {} offchain notifications",
×
2038
            offchain_notifications.len()
×
2039
        );
2040

2041
        let utxos_sent_to_self = self
12✔
2042
            .state
12✔
2043
            .lock_guard()
12✔
2044
            .await
12✔
2045
            .wallet_state
2046
            .extract_expected_utxos(
12✔
2047
                tx_outputs.clone().concat_with(maybe_change_output),
12✔
2048
                UtxoNotifier::Myself,
12✔
2049
            );
12✔
2050

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

2055
            // acquire write-lock
2056
            let mut gsm = self.state.lock_guard_mut().await;
9✔
2057

2058
            // Inform wallet of any expected incoming utxos.
2059
            // note that this (briefly) mutates self.
2060
            gsm.wallet_state
9✔
2061
                .add_expected_utxos(utxos_sent_to_self)
9✔
2062
                .await;
9✔
2063

2064
            // ensure we write new wallet state out to disk.
2065
            gsm.persist_wallet().await.expect("flushed wallet");
9✔
2066
        }
3✔
2067

2068
        // write-lock block.
2069
        {
2070
            tracing::debug!("stmi: step 6. add sent-transaction to wallet. with write-lock");
12✔
2071

2072
            let mut gsm = self.state.lock_guard_mut().await;
12✔
2073
            // inform wallet about the details of this sent transaction, so it can
2074
            // group inputs and outputs together, eg for history purposes.
2075
            let tip_digest = gsm.chain.light_state().hash();
12✔
2076
            gsm.wallet_state
12✔
2077
                .add_sent_transaction((transaction_details, tip_digest).into())
12✔
2078
                .await;
12✔
2079

2080
            tracing::debug!("stmi: step 7. flush dbs.  with write-lock");
12✔
2081
            gsm.flush_databases().await.expect("flushed DBs");
12✔
2082
        }
12✔
2083

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

2086
        // Send transaction message to main
2087
        let response = self
12✔
2088
            .rpc_server_to_main_tx
12✔
2089
            .send(RPCServerToMain::BroadcastTx(Box::new(transaction.clone())))
12✔
2090
            .await;
12✔
2091

2092
        if let Err(e) = response {
12✔
2093
            tracing::error!("Could not send Tx to main task: error: {}", e.to_string());
×
2094
            return Err(error::SendError::NotBroadcast);
×
2095
        };
12✔
2096

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

2099
        Ok((transaction, offchain_notifications))
12✔
2100
    }
21✔
2101

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

2136
        ret.map(|(tx, offchain_notifications)| (tx.kernel.txid(), offchain_notifications))
15✔
2137
    }
15✔
2138

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

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

2183
        // deserialize UtxoTransferEncrypted from bech32m string.
2184
        let utxo_transfer_encrypted = EncryptedUtxoNotification::from_bech32m(
14✔
2185
            &encrypted_utxo_notification,
14✔
2186
            self.state.cli().network,
14✔
2187
        )?;
14✔
2188

2189
        // // acquire global state read lock
2190
        let state = self.state.lock_guard().await;
14✔
2191

2192
        // find known spending key by receiver_identifier
2193
        let spending_key = state
14✔
2194
            .wallet_state
14✔
2195
            .find_known_spending_key_for_receiver_identifier(
14✔
2196
                utxo_transfer_encrypted.receiver_identifier,
14✔
2197
            )
14✔
2198
            .ok_or(error::ClaimError::UtxoUnknown)?;
14✔
2199

2200
        // decrypt utxo_transfer_encrypted into UtxoTransfer
2201
        let utxo_notification = utxo_transfer_encrypted
14✔
2202
            .decrypt_with_spending_key(&spending_key)
14✔
2203
            .expect(
14✔
2204
                "spending key should be capable of decryption because it was returned by find_known_spending_key_for_receiver_identifier",
14✔
2205
            )?;
14✔
2206

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

2209
        // search for matching monitored utxo and return early if found.
2210
        if state
14✔
2211
            .wallet_state
14✔
2212
            .find_monitored_utxo(&utxo_notification.utxo, utxo_notification.sender_randomness)
14✔
2213
            .await
14✔
2214
            .is_some()
14✔
2215
        {
2216
            info!("found monitored utxo. Returning early.");
6✔
2217
            return Ok(None);
6✔
2218
        }
8✔
2219

8✔
2220
        // construct an IncomingUtxo
8✔
2221
        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✔
2222

8✔
2223
        // Check if we can satisfy typescripts
8✔
2224
        if !incoming_utxo.utxo.all_type_script_states_are_valid() {
8✔
2225
            let err = error::ClaimError::InvalidTypeScript;
×
2226
            warn!("{}", err.to_string());
×
2227
            return Err(err);
×
2228
        }
8✔
2229

8✔
2230
        // check if wallet is already expecting this utxo.
8✔
2231
        let addition_record = incoming_utxo.addition_record();
8✔
2232
        let has_expected_utxo = state.wallet_state.has_expected_utxo(addition_record).await;
8✔
2233

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

2262
                    haystack
2✔
2263
                };
2✔
2264
                let item = Tip5::hash(&incoming_utxo.utxo);
2✔
2265
                let ams = state.chain.archival_state().archival_mutator_set.ams();
2✔
2266
                let msmp = ams
2✔
2267
                    .restore_membership_proof(
2✔
2268
                        item,
2✔
2269
                        incoming_utxo.sender_randomness,
2✔
2270
                        incoming_utxo.receiver_preimage,
2✔
2271
                        aocl_leaf_index,
2✔
2272
                    )
2✔
2273
                    .await
2✔
2274
                    .map_err(|x| anyhow!("Could not restore mutator set membership proof. Is archival mutator set corrupted? Got error: {x}"))?;
2✔
2275

2276
                let tip_digest = state.chain.light_state().hash();
2✔
2277

2✔
2278
                let mut monitored_utxo = MonitoredUtxo::new(
2✔
2279
                    incoming_utxo.utxo.clone(),
2✔
2280
                    self.state.cli().number_of_mps_per_utxo,
2✔
2281
                );
2✔
2282
                monitored_utxo.confirmed_in_block = Some((
2✔
2283
                    block.hash(),
2✔
2284
                    block.header().timestamp,
2✔
2285
                    block.header().height,
2✔
2286
                ));
2✔
2287
                monitored_utxo.add_membership_proof_for_tip(tip_digest, msmp.clone());
2✔
2288

2289
                // Was UTXO already spent? If so, register it as such.
2290
                let msa = ams.accumulator().await;
2✔
2291
                if !msa.verify(item, &msmp) {
2✔
2292
                    warn!("Claimed UTXO was already spent. Marking it as such.");
×
2293

2294
                    if let Some(spending_block) = state
×
2295
                        .chain
×
2296
                        .archival_state()
×
2297
                        .find_canonical_block_with_input(
×
2298
                            msmp.compute_indices(item),
×
2299
                            max_search_depth,
×
2300
                        )
×
2301
                        .await
×
2302
                    {
2303
                        warn!(
×
2304
                            "Claimed UTXO was spent in block {}; which has height {}",
×
2305
                            spending_block.hash(),
×
2306
                            spending_block.header().height
×
2307
                        );
2308
                        monitored_utxo.mark_as_spent(&spending_block);
×
2309
                    } else {
2310
                        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.");
×
2311
                    }
2312
                }
2✔
2313

2314
                Some(monitored_utxo)
2✔
2315
            }
2316
            None => None,
6✔
2317
        };
2318

2319
        let expected_utxo = incoming_utxo.into_expected_utxo(UtxoNotifier::Cli);
8✔
2320
        Ok(Some(ClaimUtxoData {
8✔
2321
            prepared_monitored_utxo: maybe_prepared_mutxo,
8✔
2322
            has_expected_utxo,
8✔
2323
            expected_utxo,
8✔
2324
        }))
8✔
2325
    }
14✔
2326

2327
    /// Return a PoW puzzle with the provided guesser digest.
2328
    async fn pow_puzzle_inner(
14✔
2329
        mut self,
14✔
2330
        guesser_key_after_image: Digest,
14✔
2331
        mut proposal: Block,
14✔
2332
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
14✔
2333
        let latest_block_header = *self.state.lock_guard().await.chain.light_state().header();
14✔
2334

14✔
2335
        proposal.set_header_guesser_digest(guesser_key_after_image);
14✔
2336
        let puzzle = ProofOfWorkPuzzle::new(proposal.clone(), latest_block_header);
14✔
2337

2338
        // Record block proposal in case of guesser-success, for later
2339
        // retrieval. But limit number of blocks stored this way.
2340
        let mut state = self.state.lock_guard_mut().await;
14✔
2341
        if state.mining_state.exported_block_proposals.len()
14✔
2342
            >= MAX_NUM_EXPORTED_BLOCK_PROPOSAL_STORED
14✔
2343
        {
2344
            return Err(error::RpcError::ExportedBlockProposalStorageCapacityExceeded);
×
2345
        }
14✔
2346

14✔
2347
        state
14✔
2348
            .mining_state
14✔
2349
            .exported_block_proposals
14✔
2350
            .insert(puzzle.id, proposal);
14✔
2351

14✔
2352
        Ok(Some(puzzle))
14✔
2353
    }
14✔
2354

2355
    /// get the data_directory for this neptune-core instance
2356
    pub fn data_directory(&self) -> &DataDirectory {
19✔
2357
        &self.data_directory
19✔
2358
    }
19✔
2359
}
2360

2361
impl RPC for NeptuneRPCServer {
2362
    // documented in trait. do not add doc-comment.
2363
    async fn cookie_hint(self, _: context::Context) -> RpcResult<rpc_auth::CookieHint> {
×
2364
        log_slow_scope!(fn_name!());
×
2365

×
2366
        if self.state.cli().disable_cookie_hint {
×
2367
            Err(error::RpcError::CookieHintDisabled)
×
2368
        } else {
2369
            Ok(rpc_auth::CookieHint {
×
2370
                data_directory: self.data_directory().to_owned(),
×
2371
                network: self.state.cli().network,
×
2372
            })
×
2373
        }
2374
    }
×
2375

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

6✔
2380
        Ok(self.state.cli().network)
6✔
2381
    }
6✔
2382

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

2392
        let listen_port = self.state.cli().own_listen_port();
1✔
2393
        let listen_for_peers_ip = self.state.cli().listen_addr;
1✔
2394
        Ok(listen_port.map(|port| SocketAddr::new(listen_for_peers_ip, port)))
1✔
2395
    }
1✔
2396

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

2406
        Ok(self.state.lock_guard().await.net.instance_id)
1✔
2407
    }
1✔
2408

2409
    // documented in trait. do not add doc-comment.
2410
    async fn block_height(
1✔
2411
        self,
1✔
2412
        _: context::Context,
1✔
2413
        token: rpc_auth::Token,
1✔
2414
    ) -> RpcResult<BlockHeight> {
1✔
2415
        log_slow_scope!(fn_name!());
1✔
2416
        token.auth(&self.valid_tokens)?;
1✔
2417

2418
        Ok(self
1✔
2419
            .state
1✔
2420
            .lock_guard()
1✔
2421
            .await
1✔
2422
            .chain
2423
            .light_state()
1✔
2424
            .kernel
2425
            .header
2426
            .height)
2427
    }
1✔
2428

2429
    // documented in trait. do not add doc-comment.
2430
    async fn confirmations(
×
2431
        self,
×
2432
        _: context::Context,
×
2433
        token: rpc_auth::Token,
×
2434
    ) -> RpcResult<Option<BlockHeight>> {
×
2435
        log_slow_scope!(fn_name!());
×
2436
        token.auth(&self.valid_tokens)?;
×
2437

2438
        let guard = self.state.lock_guard().await;
×
2439
        Ok(self.confirmations_internal(&guard).await)
×
2440
    }
×
2441

2442
    // documented in trait. do not add doc-comment.
2443
    async fn utxo_digest(
3✔
2444
        self,
3✔
2445
        _: context::Context,
3✔
2446
        token: rpc_auth::Token,
3✔
2447
        leaf_index: u64,
3✔
2448
    ) -> RpcResult<Option<Digest>> {
3✔
2449
        log_slow_scope!(fn_name!());
3✔
2450
        token.auth(&self.valid_tokens)?;
3✔
2451

2452
        let state = self.state.lock_guard().await;
3✔
2453
        let aocl = &state.chain.archival_state().archival_mutator_set.ams().aocl;
3✔
2454

3✔
2455
        Ok(
3✔
2456
            match leaf_index > 0 && leaf_index < aocl.num_leafs().await {
3✔
2457
                true => Some(aocl.get_leaf_async(leaf_index).await),
1✔
2458
                false => None,
2✔
2459
            },
2460
        )
2461
    }
3✔
2462

2463
    // documented in trait. do not add doc-comment.
2464
    async fn block_digest(
7✔
2465
        self,
7✔
2466
        _: context::Context,
7✔
2467
        token: rpc_auth::Token,
7✔
2468
        block_selector: BlockSelector,
7✔
2469
    ) -> RpcResult<Option<Digest>> {
7✔
2470
        log_slow_scope!(fn_name!());
7✔
2471
        token.auth(&self.valid_tokens)?;
7✔
2472

2473
        let state = self.state.lock_guard().await;
7✔
2474
        let archival_state = state.chain.archival_state();
7✔
2475
        let Some(digest) = block_selector.as_digest(&state).await else {
7✔
2476
            return Ok(None);
1✔
2477
        };
2478
        // verify the block actually exists
2479
        Ok(archival_state
6✔
2480
            .get_block_header(digest)
6✔
2481
            .await
6✔
2482
            .map(|_| digest))
6✔
2483
    }
7✔
2484

2485
    // documented in trait. do not add doc-comment.
2486
    async fn block_info(
7✔
2487
        self,
7✔
2488
        _: context::Context,
7✔
2489
        token: rpc_auth::Token,
7✔
2490
        block_selector: BlockSelector,
7✔
2491
    ) -> RpcResult<Option<BlockInfo>> {
7✔
2492
        log_slow_scope!(fn_name!());
7✔
2493
        token.auth(&self.valid_tokens)?;
7✔
2494

2495
        let state = self.state.lock_guard().await;
7✔
2496
        let Some(digest) = block_selector.as_digest(&state).await else {
7✔
2497
            return Ok(None);
1✔
2498
        };
2499
        let tip_digest = state.chain.light_state().hash();
6✔
2500
        let archival_state = state.chain.archival_state();
6✔
2501

2502
        let Some(block) = archival_state.get_block(digest).await.unwrap() else {
6✔
2503
            return Ok(None);
2✔
2504
        };
2505
        let is_canonical = archival_state
4✔
2506
            .block_belongs_to_canonical_chain(digest)
4✔
2507
            .await;
4✔
2508

2509
        // sibling blocks are those at the same height, with different digest
2510
        let sibling_blocks = archival_state
4✔
2511
            .block_height_to_block_digests(block.header().height)
4✔
2512
            .await
4✔
2513
            .into_iter()
4✔
2514
            .filter(|d| *d != digest)
4✔
2515
            .collect();
4✔
2516

4✔
2517
        Ok(Some(BlockInfo::new(
4✔
2518
            &block,
4✔
2519
            archival_state.genesis_block().hash(),
4✔
2520
            tip_digest,
4✔
2521
            sibling_blocks,
4✔
2522
            is_canonical,
4✔
2523
        )))
4✔
2524
    }
7✔
2525

2526
    // documented in trait. do not add doc-comment.
2527
    async fn public_announcements_in_block(
4✔
2528
        self,
4✔
2529
        _context: tarpc::context::Context,
4✔
2530
        token: rpc_auth::Token,
4✔
2531
        block_selector: BlockSelector,
4✔
2532
    ) -> RpcResult<Option<Vec<PublicAnnouncement>>> {
4✔
2533
        log_slow_scope!(fn_name!());
4✔
2534
        token.auth(&self.valid_tokens)?;
4✔
2535

2536
        let state = self.state.lock_guard().await;
4✔
2537
        let Some(digest) = block_selector.as_digest(&state).await else {
4✔
2538
            return Ok(None);
1✔
2539
        };
2540
        let archival_state = state.chain.archival_state();
3✔
2541
        let Some(block) = archival_state.get_block(digest).await.unwrap() else {
3✔
2542
            return Ok(None);
1✔
2543
        };
2544

2545
        Ok(Some(
2✔
2546
            block.body().transaction_kernel.public_announcements.clone(),
2✔
2547
        ))
2✔
2548
    }
4✔
2549

2550
    // documented in trait. do not add doc-comment.
2551
    async fn block_digests_by_height(
2✔
2552
        self,
2✔
2553
        _: context::Context,
2✔
2554
        token: rpc_auth::Token,
2✔
2555
        height: BlockHeight,
2✔
2556
    ) -> RpcResult<Vec<Digest>> {
2✔
2557
        log_slow_scope!(fn_name!());
2✔
2558
        token.auth(&self.valid_tokens)?;
2✔
2559

2560
        Ok(self
2✔
2561
            .state
2✔
2562
            .lock_guard()
2✔
2563
            .await
2✔
2564
            .chain
2565
            .archival_state()
2✔
2566
            .block_height_to_block_digests(height)
2✔
2567
            .await)
2✔
2568
    }
2✔
2569

2570
    // documented in trait. do not add doc-comment.
2571
    async fn latest_tip_digests(
1✔
2572
        self,
1✔
2573
        _context: tarpc::context::Context,
1✔
2574
        token: rpc_auth::Token,
1✔
2575
        n: usize,
1✔
2576
    ) -> RpcResult<Vec<Digest>> {
1✔
2577
        log_slow_scope!(fn_name!());
1✔
2578
        token.auth(&self.valid_tokens)?;
1✔
2579

2580
        let state = self.state.lock_guard().await;
1✔
2581

2582
        let latest_block_digest = state.chain.light_state().hash();
1✔
2583

1✔
2584
        Ok(state
1✔
2585
            .chain
1✔
2586
            .archival_state()
1✔
2587
            .get_ancestor_block_digests(latest_block_digest, n)
1✔
2588
            .await)
1✔
2589
    }
1✔
2590

2591
    // documented in trait. do not add doc-comment.
2592
    async fn peer_info(
1✔
2593
        self,
1✔
2594
        _: context::Context,
1✔
2595
        token: rpc_auth::Token,
1✔
2596
    ) -> RpcResult<Vec<PeerInfo>> {
1✔
2597
        log_slow_scope!(fn_name!());
1✔
2598
        token.auth(&self.valid_tokens)?;
1✔
2599

2600
        Ok(self
1✔
2601
            .state
1✔
2602
            .lock_guard()
1✔
2603
            .await
1✔
2604
            .net
2605
            .peer_map
2606
            .values()
1✔
2607
            .cloned()
1✔
2608
            .collect())
1✔
2609
    }
1✔
2610

2611
    // documented in trait. do not add doc-comment.
2612
    async fn all_punished_peers(
7✔
2613
        self,
7✔
2614
        _context: tarpc::context::Context,
7✔
2615
        token: rpc_auth::Token,
7✔
2616
    ) -> RpcResult<HashMap<IpAddr, PeerStanding>> {
7✔
2617
        log_slow_scope!(fn_name!());
7✔
2618
        token.auth(&self.valid_tokens)?;
7✔
2619

2620
        let mut sanctions_in_memory = HashMap::default();
7✔
2621

2622
        let global_state = self.state.lock_guard().await;
7✔
2623

2624
        // Get all connected peers
2625
        for (socket_address, peer_info) in &global_state.net.peer_map {
14✔
2626
            if peer_info.standing().is_negative() {
14✔
2627
                sanctions_in_memory.insert(socket_address.ip(), peer_info.standing());
7✔
2628
            }
7✔
2629
        }
2630

2631
        let sanctions_in_db = global_state.net.all_peer_sanctions_in_database();
7✔
2632

7✔
2633
        // Combine result for currently connected peers and previously connected peers but
7✔
2634
        // use result for currently connected peer if there is an overlap
7✔
2635
        let mut all_sanctions = sanctions_in_memory;
7✔
2636
        for (ip_addr, sanction) in sanctions_in_db {
12✔
2637
            if sanction.is_negative() {
5✔
2638
                all_sanctions.entry(ip_addr).or_insert(sanction);
5✔
2639
            }
5✔
2640
        }
2641

2642
        Ok(all_sanctions)
7✔
2643
    }
7✔
2644

2645
    // documented in trait. do not add doc-comment.
2646
    async fn validate_address(
1✔
2647
        self,
1✔
2648
        _ctx: context::Context,
1✔
2649
        token: rpc_auth::Token,
1✔
2650
        address_string: String,
1✔
2651
        network: Network,
1✔
2652
    ) -> RpcResult<Option<ReceivingAddress>> {
1✔
2653
        log_slow_scope!(fn_name!());
1✔
2654
        token.auth(&self.valid_tokens)?;
1✔
2655

2656
        let ret = ReceivingAddress::from_bech32m(&address_string, network).ok();
1✔
2657
        tracing::debug!(
1✔
2658
            "Responding to address validation request of {address_string}: {}",
×
2659
            ret.is_some()
×
2660
        );
2661
        Ok(ret)
1✔
2662
    }
1✔
2663

2664
    // documented in trait. do not add doc-comment.
2665
    async fn validate_amount(
×
2666
        self,
×
2667
        _ctx: context::Context,
×
2668
        token: rpc_auth::Token,
×
2669
        amount_string: String,
×
2670
    ) -> RpcResult<Option<NativeCurrencyAmount>> {
×
2671
        log_slow_scope!(fn_name!());
×
2672
        token.auth(&self.valid_tokens)?;
×
2673

2674
        // parse string
2675
        if let Ok(amt) = NativeCurrencyAmount::coins_from_str(&amount_string) {
×
2676
            Ok(Some(amt))
×
2677
        } else {
2678
            Ok(None)
×
2679
        }
2680
    }
×
2681

2682
    // documented in trait. do not add doc-comment.
2683
    async fn amount_leq_confirmed_available_balance(
×
2684
        self,
×
2685
        _ctx: context::Context,
×
2686
        token: rpc_auth::Token,
×
2687
        amount: NativeCurrencyAmount,
×
2688
    ) -> RpcResult<bool> {
×
2689
        log_slow_scope!(fn_name!());
×
2690
        token.auth(&self.valid_tokens)?;
×
2691

2692
        let gs = self.state.lock_guard().await;
×
2693
        let wallet_status = gs.get_wallet_status_for_tip().await;
×
2694

2695
        let confirmed_available = gs
×
2696
            .wallet_state
×
2697
            .confirmed_available_balance(&wallet_status, Timestamp::now());
×
2698

×
2699
        // test inequality
×
2700
        Ok(amount <= confirmed_available)
×
2701
    }
×
2702

2703
    // documented in trait. do not add doc-comment.
2704
    async fn confirmed_available_balance(
9✔
2705
        self,
9✔
2706
        _context: tarpc::context::Context,
9✔
2707
        token: rpc_auth::Token,
9✔
2708
    ) -> RpcResult<NativeCurrencyAmount> {
9✔
2709
        log_slow_scope!(fn_name!());
9✔
2710
        token.auth(&self.valid_tokens)?;
9✔
2711

2712
        let gs = self.state.lock_guard().await;
9✔
2713
        let wallet_status = gs.get_wallet_status_for_tip().await;
9✔
2714

2715
        let confirmed_available = gs
9✔
2716
            .wallet_state
9✔
2717
            .confirmed_available_balance(&wallet_status, Timestamp::now());
9✔
2718

9✔
2719
        Ok(confirmed_available)
9✔
2720
    }
9✔
2721

2722
    // documented in trait. do not add doc-comment.
2723
    async fn unconfirmed_available_balance(
×
2724
        self,
×
2725
        _context: tarpc::context::Context,
×
2726
        token: rpc_auth::Token,
×
2727
    ) -> RpcResult<NativeCurrencyAmount> {
×
2728
        log_slow_scope!(fn_name!());
×
2729
        token.auth(&self.valid_tokens)?;
×
2730

2731
        let gs = self.state.lock_guard().await;
×
2732
        let wallet_status = gs.get_wallet_status_for_tip().await;
×
2733

2734
        Ok(gs
×
2735
            .wallet_state
×
2736
            .unconfirmed_available_balance(&wallet_status, Timestamp::now()))
×
2737
    }
×
2738

2739
    // documented in trait. do not add doc-comment.
2740
    async fn wallet_status(
1✔
2741
        self,
1✔
2742
        _context: tarpc::context::Context,
1✔
2743
        token: rpc_auth::Token,
1✔
2744
    ) -> RpcResult<WalletStatus> {
1✔
2745
        log_slow_scope!(fn_name!());
1✔
2746
        token.auth(&self.valid_tokens)?;
1✔
2747

2748
        Ok(self
1✔
2749
            .state
1✔
2750
            .lock_guard()
1✔
2751
            .await
1✔
2752
            .get_wallet_status_for_tip()
1✔
2753
            .await)
1✔
2754
    }
1✔
2755

2756
    async fn num_expected_utxos(
×
2757
        self,
×
2758
        _context: tarpc::context::Context,
×
2759
        token: rpc_auth::Token,
×
2760
    ) -> RpcResult<u64> {
×
2761
        log_slow_scope!(fn_name!());
×
2762
        token.auth(&self.valid_tokens)?;
×
2763

2764
        Ok(self
×
2765
            .state
×
2766
            .lock_guard()
×
2767
            .await
×
2768
            .wallet_state
2769
            .num_expected_utxos()
×
2770
            .await)
×
2771
    }
×
2772

2773
    // documented in trait. do not add doc-comment.
2774
    async fn header(
1✔
2775
        self,
1✔
2776
        _context: tarpc::context::Context,
1✔
2777
        token: rpc_auth::Token,
1✔
2778
        block_selector: BlockSelector,
1✔
2779
    ) -> RpcResult<Option<BlockHeader>> {
1✔
2780
        log_slow_scope!(fn_name!());
1✔
2781
        token.auth(&self.valid_tokens)?;
1✔
2782

2783
        let state = self.state.lock_guard().await;
1✔
2784
        let Some(block_digest) = block_selector.as_digest(&state).await else {
1✔
2785
            return Ok(None);
×
2786
        };
2787
        Ok(state
1✔
2788
            .chain
1✔
2789
            .archival_state()
1✔
2790
            .get_block_header(block_digest)
1✔
2791
            .await)
1✔
2792
    }
1✔
2793

2794
    // documented in trait. do not add doc-comment.
2795
    async fn next_receiving_address(
12✔
2796
        mut self,
12✔
2797
        _context: tarpc::context::Context,
12✔
2798
        token: rpc_auth::Token,
12✔
2799
        key_type: KeyType,
12✔
2800
    ) -> RpcResult<Option<ReceivingAddress>> {
12✔
2801
        log_slow_scope!(fn_name!());
12✔
2802
        token.auth(&self.valid_tokens)?;
12✔
2803

2804
        let mut global_state_mut = self.state.lock_guard_mut().await;
12✔
2805

2806
        let address = global_state_mut
12✔
2807
            .wallet_state
12✔
2808
            .next_unused_spending_key(key_type)
12✔
2809
            .await
12✔
2810
            .and_then(|spending_key| spending_key.to_address());
12✔
2811

12✔
2812
        // persist wallet state to disk
12✔
2813
        global_state_mut.persist_wallet().await.expect("flushed");
12✔
2814

12✔
2815
        Ok(address)
12✔
2816
    }
12✔
2817

2818
    // documented in trait. do not add doc-comment.
2819
    async fn known_keys(
×
2820
        self,
×
2821
        _context: tarpc::context::Context,
×
2822
        token: rpc_auth::Token,
×
2823
    ) -> RpcResult<Vec<SpendingKey>> {
×
2824
        log_slow_scope!(fn_name!());
×
2825
        token.auth(&self.valid_tokens)?;
×
2826

2827
        Ok(self
×
2828
            .state
×
2829
            .lock_guard()
×
2830
            .await
×
2831
            .wallet_state
2832
            .get_all_known_spending_keys()
×
2833
            .collect())
×
2834
    }
×
2835

2836
    // documented in trait. do not add doc-comment.
2837
    async fn known_keys_by_keytype(
×
2838
        self,
×
2839
        _context: tarpc::context::Context,
×
2840
        token: rpc_auth::Token,
×
2841
        key_type: KeyType,
×
2842
    ) -> RpcResult<Vec<SpendingKey>> {
×
2843
        log_slow_scope!(fn_name!());
×
2844
        token.auth(&self.valid_tokens)?;
×
2845

2846
        Ok(self
×
2847
            .state
×
2848
            .lock_guard()
×
2849
            .await
×
2850
            .wallet_state
2851
            .get_known_spending_keys(key_type)
×
2852
            .collect())
×
2853
    }
×
2854

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

2864
        Ok(self.state.lock_guard().await.mempool.len())
1✔
2865
    }
1✔
2866

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

2876
        Ok(self.state.lock_guard().await.mempool.get_size())
1✔
2877
    }
1✔
2878

2879
    // documented in trait. do not add doc-comment.
2880
    async fn history(
1✔
2881
        self,
1✔
2882
        _context: tarpc::context::Context,
1✔
2883
        token: rpc_auth::Token,
1✔
2884
    ) -> RpcResult<Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)>> {
1✔
2885
        log_slow_scope!(fn_name!());
1✔
2886
        token.auth(&self.valid_tokens)?;
1✔
2887

2888
        let history = self.state.lock_guard().await.get_balance_history().await;
1✔
2889

2890
        // sort
2891
        let mut display_history: Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)> =
1✔
2892
            history
1✔
2893
                .iter()
1✔
2894
                .map(|(h, t, bh, a)| (*h, *bh, *t, *a))
1✔
2895
                .collect::<Vec<_>>();
1✔
2896
        display_history.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
1✔
2897

1✔
2898
        // return
1✔
2899
        Ok(display_history)
1✔
2900
    }
1✔
2901

2902
    // documented in trait. do not add doc-comment.
2903
    async fn dashboard_overview_data(
1✔
2904
        self,
1✔
2905
        _context: tarpc::context::Context,
1✔
2906
        token: rpc_auth::Token,
1✔
2907
    ) -> RpcResult<DashBoardOverviewDataFromClient> {
1✔
2908
        log_slow_scope!(fn_name!());
1✔
2909
        token.auth(&self.valid_tokens)?;
1✔
2910

2911
        let now = Timestamp::now();
1✔
2912
        let state = self.state.lock_guard().await;
1✔
2913
        let tip_digest = {
1✔
2914
            log_slow_scope!(fn_name!() + "::hash() tip digest");
1✔
2915
            state.chain.light_state().hash()
1✔
2916
        };
1✔
2917
        let tip_header = *state.chain.light_state().header();
1✔
2918
        let syncing = state.net.sync_anchor.is_some();
1✔
2919
        let mempool_size = {
1✔
2920
            log_slow_scope!(fn_name!() + "::mempool.get_size()");
1✔
2921
            state.mempool.get_size()
1✔
2922
        };
1✔
2923
        let mempool_total_tx_count = {
1✔
2924
            log_slow_scope!(fn_name!() + "::mempool.len()");
1✔
2925
            state.mempool.len()
1✔
2926
        };
1✔
2927
        let mempool_own_tx_count = {
1✔
2928
            log_slow_scope!(fn_name!() + "::mempool.num_own_txs()");
1✔
2929
            state.mempool.num_own_txs()
1✔
2930
        };
1✔
2931
        let cpu_temp = None; // disable for now.  call is too slow.
1✔
2932
        let proving_capability = self.state.cli().proving_capability();
1✔
2933

1✔
2934
        info!("proving capability: {proving_capability}");
1✔
2935

2936
        let peer_count = Some(state.net.peer_map.len());
1✔
2937
        let max_num_peers = self.state.cli().max_num_peers;
1✔
2938

1✔
2939
        let mining_status = Some(state.mining_state.mining_status);
1✔
2940

2941
        let confirmations = {
1✔
2942
            log_slow_scope!(fn_name!() + "::confirmations_internal()");
1✔
2943
            self.confirmations_internal(&state).await
1✔
2944
        };
2945

2946
        let wallet_status = {
1✔
2947
            log_slow_scope!(fn_name!() + "::get_wallet_status_for_tip()");
1✔
2948
            state.get_wallet_status_for_tip().await
1✔
2949
        };
2950
        let wallet_state = &state.wallet_state;
1✔
2951

1✔
2952
        let confirmed_available_balance = {
1✔
2953
            log_slow_scope!(fn_name!() + "::confirmed_available_balance()");
1✔
2954
            wallet_state.confirmed_available_balance(&wallet_status, now)
1✔
2955
        };
1✔
2956
        let confirmed_total_balance = {
1✔
2957
            log_slow_scope!(fn_name!() + "::confirmed_total_balance()");
1✔
2958
            wallet_state.confirmed_total_balance(&wallet_status)
1✔
2959
        };
1✔
2960

1✔
2961
        let unconfirmed_available_balance = {
1✔
2962
            log_slow_scope!(fn_name!() + "::unconfirmed_available_balance()");
1✔
2963
            wallet_state.unconfirmed_available_balance(&wallet_status, now)
1✔
2964
        };
1✔
2965
        let unconfirmed_total_balance = {
1✔
2966
            log_slow_scope!(fn_name!() + "::unconfirmed_total_balance()");
1✔
2967
            wallet_state.unconfirmed_total_balance(&wallet_status)
1✔
2968
        };
1✔
2969

1✔
2970
        Ok(DashBoardOverviewDataFromClient {
1✔
2971
            tip_digest,
1✔
2972
            tip_header,
1✔
2973
            syncing,
1✔
2974
            confirmed_available_balance,
1✔
2975
            confirmed_total_balance,
1✔
2976
            unconfirmed_available_balance,
1✔
2977
            unconfirmed_total_balance,
1✔
2978
            mempool_size,
1✔
2979
            mempool_total_tx_count,
1✔
2980
            mempool_own_tx_count,
1✔
2981
            peer_count,
1✔
2982
            max_num_peers,
1✔
2983
            mining_status,
1✔
2984
            proving_capability,
1✔
2985
            confirmations,
1✔
2986
            cpu_temp,
1✔
2987
        })
1✔
2988
    }
1✔
2989

2990
    /******** CHANGE THINGS ********/
2991
    // Locking:
2992
    //   * acquires `global_state_lock` for write
2993
    //
2994
    // documented in trait. do not add doc-comment.
2995
    async fn clear_all_standings(
2✔
2996
        mut self,
2✔
2997
        _: context::Context,
2✔
2998
        token: rpc_auth::Token,
2✔
2999
    ) -> RpcResult<()> {
2✔
3000
        log_slow_scope!(fn_name!());
2✔
3001
        token.auth(&self.valid_tokens)?;
2✔
3002

3003
        let mut global_state_mut = self.state.lock_guard_mut().await;
2✔
3004
        global_state_mut
2✔
3005
            .net
2✔
3006
            .peer_map
2✔
3007
            .iter_mut()
2✔
3008
            .for_each(|(_, peerinfo)| {
4✔
3009
                peerinfo.standing.clear_standing();
4✔
3010
            });
4✔
3011

2✔
3012
        // iterates and modifies standing field for all connected peers
2✔
3013
        global_state_mut.net.clear_all_standings_in_database().await;
2✔
3014

3015
        Ok(global_state_mut.flush_databases().await?)
2✔
3016
    }
2✔
3017

3018
    // Locking:
3019
    //   * acquires `global_state_lock` for write
3020
    //
3021
    // documented in trait. do not add doc-comment.
3022
    async fn clear_standing_by_ip(
2✔
3023
        mut self,
2✔
3024
        _: context::Context,
2✔
3025
        token: rpc_auth::Token,
2✔
3026
        ip: IpAddr,
2✔
3027
    ) -> RpcResult<()> {
2✔
3028
        log_slow_scope!(fn_name!());
2✔
3029
        token.auth(&self.valid_tokens)?;
2✔
3030

3031
        let mut global_state_mut = self.state.lock_guard_mut().await;
2✔
3032
        global_state_mut
2✔
3033
            .net
2✔
3034
            .peer_map
2✔
3035
            .iter_mut()
2✔
3036
            .for_each(|(socketaddr, peerinfo)| {
4✔
3037
                if socketaddr.ip() == ip {
4✔
3038
                    peerinfo.standing.clear_standing();
1✔
3039
                }
3✔
3040
            });
4✔
3041

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

3045
        Ok(global_state_mut.flush_databases().await?)
2✔
3046
    }
2✔
3047

3048
    // documented in trait. do not add doc-comment.
3049
    async fn send(
2✔
3050
        self,
2✔
3051
        ctx: context::Context,
2✔
3052
        token: rpc_auth::Token,
2✔
3053
        amount: NativeCurrencyAmount,
2✔
3054
        address: ReceivingAddress,
2✔
3055
        owned_utxo_notify_method: UtxoNotificationMedium,
2✔
3056
        unowned_utxo_notify_medium: UtxoNotificationMedium,
2✔
3057
        fee: NativeCurrencyAmount,
2✔
3058
    ) -> RpcResult<(TransactionKernelId, Vec<PrivateNotificationData>)> {
2✔
3059
        log_slow_scope!(fn_name!());
2✔
3060

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

2✔
3063
        self.send_to_many(
2✔
3064
            ctx,
2✔
3065
            token,
2✔
3066
            vec![(address, amount)],
2✔
3067
            owned_utxo_notify_method,
2✔
3068
            unowned_utxo_notify_medium,
2✔
3069
            fee,
2✔
3070
        )
2✔
3071
        .await
2✔
3072
    }
2✔
3073

3074
    // Locking:
3075
    //   * acquires `global_state_lock` for write
3076
    //
3077
    // TODO: add an endpoint to get recommended fee density.
3078
    //
3079
    // documented in trait. do not add doc-comment.
3080
    async fn send_to_many(
3✔
3081
        self,
3✔
3082
        ctx: context::Context,
3✔
3083
        token: rpc_auth::Token,
3✔
3084
        outputs: Vec<(ReceivingAddress, NativeCurrencyAmount)>,
3✔
3085
        owned_utxo_notification_medium: UtxoNotificationMedium,
3✔
3086
        unowned_utxo_notification_medium: UtxoNotificationMedium,
3✔
3087
        fee: NativeCurrencyAmount,
3✔
3088
    ) -> RpcResult<(TransactionKernelId, Vec<PrivateNotificationData>)> {
3✔
3089
        log_slow_scope!(fn_name!());
3✔
3090
        token.auth(&self.valid_tokens)?;
3✔
3091

3092
        tracing::debug!("stm: entered fn");
3✔
3093

3094
        if self.state.cli().no_transaction_initiation {
3✔
3095
            warn!("Cannot initiate transaction because `--no-transaction-initiation` flag is set.");
2✔
3096
            return Err(error::SendError::Unsupported.into());
2✔
3097
        }
1✔
3098

1✔
3099
        // abort early on negative fee
1✔
3100
        if fee.is_negative() {
1✔
3101
            warn!("Cannot send negative-fee transaction.");
×
3102
            return Err(error::SendError::NegativeFee.into());
×
3103
        }
1✔
3104

1✔
3105
        match self.state.cli().proving_capability() {
1✔
3106
            TxProvingCapability::LockScript | TxProvingCapability::PrimitiveWitness => {
3107
                warn!("Cannot initiate transaction because transaction proving capability is too weak.");
1✔
3108
                return Err(error::SendError::TooWeak.into());
1✔
3109
            }
3110
            TxProvingCapability::ProofCollection | TxProvingCapability::SingleProof => (),
×
3111
        };
×
3112

×
3113
        // The proving capability is set to the lowest possible value here,
×
3114
        // since we don't want the client (CLI or dashboard) to hang while
×
3115
        // producing proofs. Instead, we let (a task started by) main loop
×
3116
        // handle the proving.
×
3117
        let tx_proving_capability = TxProvingCapability::PrimitiveWitness;
×
3118
        Ok(self
×
3119
            .send_to_many_inner(
×
3120
                ctx,
×
3121
                outputs,
×
3122
                (
×
3123
                    owned_utxo_notification_medium,
×
3124
                    unowned_utxo_notification_medium,
×
3125
                ),
×
3126
                fee,
×
3127
                Timestamp::now(),
×
3128
                tx_proving_capability,
×
3129
            )
×
3130
            .await?)
×
3131
    }
3✔
3132

3133
    // // documented in trait. do not add doc-comment.
3134
    async fn claim_utxo(
14✔
3135
        mut self,
14✔
3136
        _ctx: context::Context,
14✔
3137
        token: rpc_auth::Token,
14✔
3138
        encrypted_utxo_notification: String,
14✔
3139
        max_search_depth: Option<u64>,
14✔
3140
    ) -> RpcResult<bool> {
14✔
3141
        log_slow_scope!(fn_name!());
14✔
3142
        token.auth(&self.valid_tokens)?;
14✔
3143

3144
        let claim_data = self
14✔
3145
            .claim_utxo_inner(encrypted_utxo_notification, max_search_depth)
14✔
3146
            .await?;
14✔
3147

3148
        let Some(claim_data) = claim_data else {
14✔
3149
            // UTXO has already been claimed by wallet
3150
            warn!("UTXO notification of amount was already received. Not adding again.");
6✔
3151
            return Ok(false);
6✔
3152
        };
3153

3154
        let expected_utxo_was_new = !claim_data.has_expected_utxo;
8✔
3155
        self.state
8✔
3156
            .lock_guard_mut()
8✔
3157
            .await
8✔
3158
            .wallet_state
3159
            .claim_utxo(claim_data)
8✔
3160
            .await
8✔
3161
            .map_err(error::ClaimError::from)?;
8✔
3162

3163
        Ok(expected_utxo_was_new)
8✔
3164
    }
14✔
3165

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

3171
        // 1. Send shutdown message to main
3172
        let response = self
1✔
3173
            .rpc_server_to_main_tx
1✔
3174
            .send(RPCServerToMain::Shutdown)
1✔
3175
            .await;
1✔
3176

3177
        // 2. Send acknowledgement to client.
3178
        Ok(response.is_ok())
1✔
3179
    }
1✔
3180

3181
    // documented in trait. do not add doc-comment.
NEW
3182
    async fn clear_mempool(
×
NEW
3183
        self,
×
NEW
3184
        _context: tarpc::context::Context,
×
NEW
3185
        token: rpc_auth::Token,
×
NEW
3186
    ) -> RpcResult<()> {
×
NEW
3187
        log_slow_scope!(fn_name!());
×
NEW
3188
        token.auth(&self.valid_tokens)?;
×
3189

NEW
3190
        let _ = self
×
NEW
3191
            .rpc_server_to_main_tx
×
NEW
3192
            .send(RPCServerToMain::ClearMempool)
×
NEW
3193
            .await;
×
3194

NEW
3195
        Ok(())
×
NEW
3196
    }
×
3197

3198
    async fn pause_miner(
1✔
3199
        self,
1✔
3200
        _context: tarpc::context::Context,
1✔
3201
        token: rpc_auth::Token,
1✔
3202
    ) -> RpcResult<()> {
1✔
3203
        log_slow_scope!(fn_name!());
1✔
3204
        token.auth(&self.valid_tokens)?;
1✔
3205

3206
        if self.state.cli().mine() {
1✔
3207
            let _ = self
×
3208
                .rpc_server_to_main_tx
×
3209
                .send(RPCServerToMain::PauseMiner)
×
3210
                .await;
×
3211
        } else {
3212
            info!("Cannot pause miner since it was never started");
1✔
3213
        }
3214
        Ok(())
1✔
3215
    }
1✔
3216

3217
    // documented in trait. do not add doc-comment.
3218
    async fn restart_miner(
1✔
3219
        self,
1✔
3220
        _context: tarpc::context::Context,
1✔
3221
        token: rpc_auth::Token,
1✔
3222
    ) -> RpcResult<()> {
1✔
3223
        log_slow_scope!(fn_name!());
1✔
3224
        token.auth(&self.valid_tokens)?;
1✔
3225

3226
        if self.state.cli().mine() {
1✔
3227
            let _ = self
×
3228
                .rpc_server_to_main_tx
×
3229
                .send(RPCServerToMain::RestartMiner)
×
3230
                .await;
×
3231
        } else {
3232
            info!("Cannot restart miner since it was never started");
1✔
3233
        }
3234
        Ok(())
1✔
3235
    }
1✔
3236

3237
    // documented in trait. do not add doc-comment.
3238
    async fn provide_pow_solution(
6✔
3239
        self,
6✔
3240
        _context: tarpc::context::Context,
6✔
3241
        token: rpc_auth::Token,
6✔
3242
        nonce: Digest,
6✔
3243
        proposal_id: Digest,
6✔
3244
    ) -> RpcResult<bool> {
6✔
3245
        log_slow_scope!(fn_name!());
6✔
3246
        token.auth(&self.valid_tokens)?;
6✔
3247

3248
        // Find proposal from list of exported proposals.
3249
        let Some(mut proposal) = self
6✔
3250
            .state
6✔
3251
            .lock_guard()
6✔
3252
            .await
6✔
3253
            .mining_state
3254
            .exported_block_proposals
3255
            .get(&proposal_id)
6✔
3256
            .map(|x| x.to_owned())
6✔
3257
        else {
3258
            warn!(
2✔
3259
                "Got claimed PoW solution but no challenge was known. \
×
3260
                Did solution come in too late?"
×
3261
            );
3262
            return Ok(false);
2✔
3263
        };
3264

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

4✔
3268
        proposal.set_header_nonce(nonce);
4✔
3269
        let threshold = latest_block_header.difficulty.target();
4✔
3270
        let solution_digest = proposal.hash();
4✔
3271
        if solution_digest > threshold {
4✔
3272
            warn!(
2✔
3273
                "Got claimed PoW solution but PoW threshold was not met.\n\
×
3274
            Claimed solution: {solution_digest};\nthreshold: {threshold}"
×
3275
            );
3276
            return Ok(false);
2✔
3277
        }
2✔
3278

2✔
3279
        // No time to waste! Inform main_loop!
2✔
3280
        let solution = Box::new(proposal);
2✔
3281
        let _ = self
2✔
3282
            .rpc_server_to_main_tx
2✔
3283
            .send(RPCServerToMain::ProofOfWorkSolution(solution))
2✔
3284
            .await;
2✔
3285

3286
        Ok(true)
2✔
3287
    }
6✔
3288

3289
    // documented in trait. do not add doc-comment.
3290
    async fn prune_abandoned_monitored_utxos(
1✔
3291
        mut self,
1✔
3292
        _context: tarpc::context::Context,
1✔
3293
        token: rpc_auth::Token,
1✔
3294
    ) -> RpcResult<usize> {
1✔
3295
        const DEFAULT_MUTXO_PRUNE_DEPTH: usize = 200;
3296

3297
        log_slow_scope!(fn_name!());
1✔
3298
        token.auth(&self.valid_tokens)?;
1✔
3299

3300
        let mut global_state_mut = self.state.lock_guard_mut().await;
1✔
3301

3302
        let prune_count_res = global_state_mut
1✔
3303
            .prune_abandoned_monitored_utxos(DEFAULT_MUTXO_PRUNE_DEPTH)
1✔
3304
            .await;
1✔
3305

3306
        global_state_mut
1✔
3307
            .flush_databases()
1✔
3308
            .await
1✔
3309
            .expect("flushed DBs");
1✔
3310

1✔
3311
        match prune_count_res {
1✔
3312
            Ok(prune_count) => {
1✔
3313
                info!("Marked {prune_count} monitored UTXOs as abandoned");
1✔
3314
                Ok(prune_count)
1✔
3315
            }
3316
            Err(err) => {
×
3317
                error!("Pruning monitored UTXOs failed with error: {err}");
×
3318
                Ok(0)
×
3319
            }
3320
        }
3321
    }
1✔
3322

3323
    // documented in trait. do not add doc-comment.
3324
    async fn list_own_coins(
×
3325
        self,
×
3326
        _context: ::tarpc::context::Context,
×
3327
        token: rpc_auth::Token,
×
3328
    ) -> RpcResult<Vec<CoinWithPossibleTimeLock>> {
×
3329
        log_slow_scope!(fn_name!());
×
3330
        token.auth(&self.valid_tokens)?;
×
3331

3332
        let state = self.state.lock_guard().await;
×
3333
        let tip = state.chain.light_state();
×
3334
        let tip_hash = tip.hash();
×
3335
        let tip_msa = tip.mutator_set_accumulator_after();
×
3336

×
3337
        Ok(state
×
3338
            .wallet_state
×
3339
            .get_all_own_coins_with_possible_timelocks(&tip_msa, tip_hash)
×
3340
            .await)
×
3341
    }
×
3342

3343
    // documented in trait. do not add doc-comment.
3344
    async fn cpu_temp(
1✔
3345
        self,
1✔
3346
        _context: tarpc::context::Context,
1✔
3347
        token: rpc_auth::Token,
1✔
3348
    ) -> RpcResult<Option<f32>> {
1✔
3349
        log_slow_scope!(fn_name!());
1✔
3350
        token.auth(&self.valid_tokens)?;
1✔
3351

3352
        Ok(Self::cpu_temp_inner())
1✔
3353
    }
1✔
3354

3355
    // documented in trait. do not add doc-comment.
3356
    async fn pow_puzzle_internal_key(
2✔
3357
        self,
2✔
3358
        _context: tarpc::context::Context,
2✔
3359
        token: rpc_auth::Token,
2✔
3360
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
2✔
3361
        log_slow_scope!(fn_name!());
2✔
3362
        token.auth(&self.valid_tokens)?;
2✔
3363

3364
        let Some(proposal) = self
2✔
3365
            .state
2✔
3366
            .lock_guard()
2✔
3367
            .await
2✔
3368
            .mining_state
3369
            .block_proposal
3370
            .map(|x| x.to_owned())
2✔
3371
        else {
3372
            return Ok(None);
1✔
3373
        };
3374

3375
        let guesser_key_after_image = self
1✔
3376
            .state
1✔
3377
            .lock_guard()
1✔
3378
            .await
1✔
3379
            .wallet_state
3380
            .wallet_entropy
3381
            .guesser_spending_key(proposal.header().prev_block_digest)
1✔
3382
            .after_image();
1✔
3383

1✔
3384
        self.pow_puzzle_inner(guesser_key_after_image, proposal)
1✔
3385
            .await
1✔
3386
    }
2✔
3387

3388
    // documented in trait. do not add doc-comment.
3389
    async fn pow_puzzle_external_key(
14✔
3390
        self,
14✔
3391
        _context: tarpc::context::Context,
14✔
3392
        token: rpc_auth::Token,
14✔
3393
        guesser_digest: Digest,
14✔
3394
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
14✔
3395
        log_slow_scope!(fn_name!());
14✔
3396
        token.auth(&self.valid_tokens)?;
14✔
3397

3398
        let Some(proposal) = self
14✔
3399
            .state
14✔
3400
            .lock_guard()
14✔
3401
            .await
14✔
3402
            .mining_state
3403
            .block_proposal
3404
            .map(|x| x.to_owned())
14✔
3405
        else {
3406
            return Ok(None);
1✔
3407
        };
3408

3409
        self.pow_puzzle_inner(guesser_digest, proposal).await
13✔
3410
    }
14✔
3411

3412
    // documented in trait. do not add doc-comment.
3413
    async fn block_intervals(
1✔
3414
        self,
1✔
3415
        _context: tarpc::context::Context,
1✔
3416
        token: rpc_auth::Token,
1✔
3417
        last_block: BlockSelector,
1✔
3418
        max_num_blocks: Option<usize>,
1✔
3419
    ) -> RpcResult<Option<Vec<(u64, u64)>>> {
1✔
3420
        log_slow_scope!(fn_name!());
1✔
3421
        token.auth(&self.valid_tokens)?;
1✔
3422

3423
        let state = self.state.lock_guard().await;
1✔
3424
        let Some(last_block) = last_block.as_digest(&state).await else {
1✔
3425
            return Ok(None);
×
3426
        };
3427
        let mut intervals = vec![];
1✔
3428
        let mut current = state
1✔
3429
            .chain
1✔
3430
            .archival_state()
1✔
3431
            .get_block_header(last_block)
1✔
3432
            .await
1✔
3433
            .expect("If digest can be found, block header should also be known");
1✔
3434
        let mut parent = state
1✔
3435
            .chain
1✔
3436
            .archival_state()
1✔
3437
            .get_block_header(current.prev_block_digest)
1✔
3438
            .await;
1✔
3439

3440
        // Exclude genesis since it was not mined. So block interval 0-->1
3441
        // is not included.
3442
        while parent.is_some()
1✔
3443
            && !parent.unwrap().height.is_genesis()
×
3444
            && max_num_blocks.is_none_or(|max_num| max_num > intervals.len())
×
3445
        {
3446
            let parent_ = parent.unwrap();
×
3447
            let interval = current.timestamp.to_millis() - parent_.timestamp.to_millis();
×
3448
            let block_height: u64 = current.height.into();
×
3449
            intervals.push((block_height, interval));
×
3450
            current = parent_;
×
3451
            parent = state
×
3452
                .chain
×
3453
                .archival_state()
×
3454
                .get_block_header(current.prev_block_digest)
×
3455
                .await;
×
3456
        }
3457

3458
        Ok(Some(intervals))
1✔
3459
    }
1✔
3460

3461
    async fn block_difficulties(
1✔
3462
        self,
1✔
3463
        _context: tarpc::context::Context,
1✔
3464
        token: rpc_auth::Token,
1✔
3465
        last_block: BlockSelector,
1✔
3466
        max_num_blocks: Option<usize>,
1✔
3467
    ) -> RpcResult<Vec<(u64, Difficulty)>> {
1✔
3468
        log_slow_scope!(fn_name!());
1✔
3469
        token.auth(&self.valid_tokens)?;
1✔
3470

3471
        let state = self.state.lock_guard().await;
1✔
3472
        let last_block = last_block.as_digest(&state).await;
1✔
3473
        let Some(last_block) = last_block else {
1✔
3474
            return Ok(vec![]);
×
3475
        };
3476

3477
        let mut difficulties = vec![];
1✔
3478

3479
        let mut current = state
1✔
3480
            .chain
1✔
3481
            .archival_state()
1✔
3482
            .get_block_header(last_block)
1✔
3483
            .await;
1✔
3484
        while current.is_some()
2✔
3485
            && max_num_blocks.is_none_or(|max_num| max_num >= difficulties.len())
1✔
3486
        {
3487
            let current_ = current.unwrap();
1✔
3488
            let height: u64 = current_.height.into();
1✔
3489
            difficulties.push((height, current_.difficulty));
1✔
3490
            current = state
1✔
3491
                .chain
1✔
3492
                .archival_state()
1✔
3493
                .get_block_header(current_.prev_block_digest)
1✔
3494
                .await;
1✔
3495
        }
3496

3497
        Ok(difficulties)
1✔
3498
    }
1✔
3499

3500
    // documented in trait. do not add doc-comment.
3501
    async fn broadcast_all_mempool_txs(
1✔
3502
        self,
1✔
3503
        _context: tarpc::context::Context,
1✔
3504
        token: rpc_auth::Token,
1✔
3505
    ) -> RpcResult<()> {
1✔
3506
        log_slow_scope!(fn_name!());
1✔
3507
        token.auth(&self.valid_tokens)?;
1✔
3508

3509
        // If this sending fails, it means `main_loop` is no longer running,
3510
        // and node is crashed. No reason to log anything additional.
3511
        let _ = self
1✔
3512
            .rpc_server_to_main_tx
1✔
3513
            .send(RPCServerToMain::BroadcastMempoolTransactions)
1✔
3514
            .await;
1✔
3515

3516
        Ok(())
1✔
3517
    }
1✔
3518

3519
    // documented in trait. do not add doc-comment.
3520
    async fn mempool_overview(
1✔
3521
        self,
1✔
3522
        _context: ::tarpc::context::Context,
1✔
3523
        token: rpc_auth::Token,
1✔
3524
        start_index: usize,
1✔
3525
        number: usize,
1✔
3526
    ) -> RpcResult<Vec<MempoolTransactionInfo>> {
1✔
3527
        log_slow_scope!(fn_name!());
1✔
3528
        token.auth(&self.valid_tokens)?;
1✔
3529

3530
        let global_state = self.state.lock_guard().await;
1✔
3531
        let mempool_txkids = global_state
1✔
3532
            .mempool
1✔
3533
            .get_sorted_iter()
1✔
3534
            .skip(start_index)
1✔
3535
            .take(number)
1✔
3536
            .map(|(txkid, _)| txkid)
1✔
3537
            .collect_vec();
1✔
3538

1✔
3539
        let (incoming, outgoing): (HashMap<_, _>, HashMap<_, _>) = {
1✔
3540
            let (incoming_iter, outgoing_iter) =
1✔
3541
                global_state.wallet_state.mempool_balance_updates();
1✔
3542
            (incoming_iter.collect(), outgoing_iter.collect())
1✔
3543
        };
1✔
3544

1✔
3545
        let tip_msah = global_state
1✔
3546
            .chain
1✔
3547
            .light_state()
1✔
3548
            .mutator_set_accumulator_after()
1✔
3549
            .hash();
1✔
3550

1✔
3551
        let mempool_transactions = mempool_txkids
1✔
3552
            .iter()
1✔
3553
            .filter_map(|id| {
1✔
3554
                let mut mptxi = global_state
×
3555
                    .mempool
×
3556
                    .get(*id)
×
3557
                    .map(|tx| (MempoolTransactionInfo::from(tx), tx.kernel.mutator_set_hash))
×
3558
                    .map(|(mptxi, tx_msah)| {
×
3559
                        if tx_msah == tip_msah {
×
3560
                            mptxi.synced()
×
3561
                        } else {
3562
                            mptxi
×
3563
                        }
3564
                    });
×
3565
                if mptxi.is_some() {
×
3566
                    if let Some(pos_effect) = incoming.get(id) {
×
3567
                        mptxi = Some(mptxi.unwrap().with_positive_effect_on_balance(*pos_effect));
×
3568
                    }
×
3569
                    if let Some(neg_effect) = outgoing.get(id) {
×
3570
                        mptxi = Some(mptxi.unwrap().with_negative_effect_on_balance(*neg_effect));
×
3571
                    }
×
3572
                }
×
3573

3574
                mptxi
×
3575
            })
1✔
3576
            .collect_vec();
1✔
3577

1✔
3578
        Ok(mempool_transactions)
1✔
3579
    }
1✔
3580
}
3581

3582
pub mod error {
3583
    use super::*;
3584

3585
    /// enumerates possible rpc api errors
3586
    #[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
3587
    #[non_exhaustive]
3588
    pub enum RpcError {
3589
        // auth error
3590
        #[error(transparent)]
3591
        Auth(#[from] rpc_auth::error::AuthError),
3592

3593
        // catch-all error, eg for anyhow errors
3594
        #[error("rpc call failed")]
3595
        Failed(String),
3596

3597
        // API specific error variants.
3598
        #[error("cookie hints are disabled on this node")]
3599
        CookieHintDisabled,
3600

3601
        #[error("capacity to store exported block proposals exceeded")]
3602
        ExportedBlockProposalStorageCapacityExceeded,
3603

3604
        #[error(transparent)]
3605
        SendError(#[from] SendError),
3606

3607
        #[error(transparent)]
3608
        ClaimError(#[from] ClaimError),
3609
    }
3610

3611
    // convert anyhow::Error to an RpcError::Failed.
3612
    // note that anyhow Error is not serializable.
3613
    impl From<anyhow::Error> for RpcError {
3614
        fn from(e: anyhow::Error) -> Self {
×
3615
            Self::Failed(e.to_string())
×
3616
        }
×
3617
    }
3618

3619
    /// enumerates possible transaction send errors
3620
    #[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
3621
    #[non_exhaustive]
3622
    pub enum SendError {
3623
        #[error("send() is not supported by this node")]
3624
        Unsupported,
3625

3626
        #[error("transaction could not be broadcast.")]
3627
        NotBroadcast,
3628

3629
        // catch-all error, eg for anyhow errors
3630
        #[error("transaction could not be sent.  reason: {0}")]
3631
        Failed(String),
3632

3633
        #[error("Transaction with negative fees not allowed")]
3634
        NegativeFee,
3635

3636
        #[error("machine too weak to initiate transactions")]
3637
        TooWeak,
3638

3639
        #[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())]
3640
        RateLimit {
3641
            height: BlockHeight,
3642
            tip_digest: Digest,
3643
            max: usize,
3644
        },
3645
    }
3646

3647
    // convert anyhow::Error to a SendError::Failed.
3648
    // note that anyhow Error is not serializable.
3649
    impl From<anyhow::Error> for SendError {
3650
        fn from(e: anyhow::Error) -> Self {
1✔
3651
            Self::Failed(e.to_string())
1✔
3652
        }
1✔
3653
    }
3654

3655
    /// enumerates possible transaction send errors
3656
    #[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)]
3657
    #[non_exhaustive]
3658
    pub enum ClaimError {
3659
        #[error("utxo does not match any known wallet key")]
3660
        UtxoUnknown,
3661

3662
        #[error("invalid type script in claim utxo")]
3663
        InvalidTypeScript,
3664

3665
        // catch-all error, eg for anyhow errors
3666
        #[error("claim unsuccessful")]
3667
        Failed(String),
3668
    }
3669

3670
    // convert anyhow::Error to a ClaimError::Failed.
3671
    // note that anyhow Error is not serializable.
3672
    impl From<anyhow::Error> for ClaimError {
3673
        fn from(e: anyhow::Error) -> Self {
×
3674
            Self::Failed(e.to_string())
×
3675
        }
×
3676
    }
3677
}
3678

3679
#[cfg(test)]
3680
mod rpc_server_tests {
3681
    use anyhow::Result;
3682
    use num_traits::One;
3683
    use num_traits::Zero;
3684
    use rand::rngs::StdRng;
3685
    use rand::Rng;
3686
    use rand::SeedableRng;
3687
    use strum::IntoEnumIterator;
3688
    use tracing_test::traced_test;
3689

3690
    use super::*;
3691
    use crate::config_models::cli_args;
3692
    use crate::config_models::network::Network;
3693
    use crate::database::storage::storage_vec::traits::*;
3694
    use crate::models::blockchain::transaction::transaction_kernel::transaction_kernel_tests::pseudorandom_transaction_kernel;
3695
    use crate::models::peer::NegativePeerSanction;
3696
    use crate::models::peer::PeerSanction;
3697
    use crate::models::state::wallet::address::generation_address::GenerationSpendingKey;
3698
    use crate::models::state::wallet::wallet_entropy::WalletEntropy;
3699
    use crate::rpc_server::NeptuneRPCServer;
3700
    use crate::tests::shared::invalid_block_with_transaction;
3701
    use crate::tests::shared::make_mock_block;
3702
    use crate::tests::shared::mock_genesis_global_state;
3703
    use crate::tests::shared::unit_test_data_directory;
3704
    use crate::Block;
3705
    use crate::RPC_CHANNEL_CAPACITY;
3706

3707
    async fn test_rpc_server(
29✔
3708
        network: Network,
29✔
3709
        wallet_entropy: WalletEntropy,
29✔
3710
        peer_count: u8,
29✔
3711
        cli: cli_args::Args,
29✔
3712
    ) -> NeptuneRPCServer {
29✔
3713
        let global_state_lock =
29✔
3714
            mock_genesis_global_state(network, peer_count, wallet_entropy, cli).await;
29✔
3715
        let (dummy_tx, mut dummy_rx) =
29✔
3716
            tokio::sync::mpsc::channel::<RPCServerToMain>(RPC_CHANNEL_CAPACITY);
29✔
3717

29✔
3718
        tokio::spawn(async move {
29✔
3719
            while let Some(i) = dummy_rx.recv().await {
40✔
3720
                tracing::trace!("mock Main got message = {:?}", i);
11✔
3721
            }
3722
        });
29✔
3723

29✔
3724
        let data_directory = unit_test_data_directory(network).unwrap();
29✔
3725

3726
        let valid_tokens: Vec<rpc_auth::Token> = vec![rpc_auth::Cookie::try_new(&data_directory)
29✔
3727
            .await
29✔
3728
            .unwrap()
29✔
3729
            .into()];
29✔
3730

29✔
3731
        NeptuneRPCServer::new(global_state_lock, dummy_tx, data_directory, valid_tokens)
29✔
3732
    }
29✔
3733

3734
    async fn cookie_token(server: &NeptuneRPCServer) -> rpc_auth::Token {
19✔
3735
        rpc_auth::Cookie::try_load(server.data_directory())
19✔
3736
            .await
19✔
3737
            .unwrap()
19✔
3738
            .into()
19✔
3739
    }
19✔
3740

3741
    #[tokio::test]
3742
    async fn network_response_is_consistent() -> Result<()> {
1✔
3743
        // Verify that a wallet not receiving a premine is empty at startup
1✔
3744
        for network in Network::iter() {
6✔
3745
            let rpc_server = test_rpc_server(
5✔
3746
                network,
5✔
3747
                WalletEntropy::new_random(),
5✔
3748
                2,
5✔
3749
                cli_args::Args {
5✔
3750
                    network,
5✔
3751
                    ..Default::default()
5✔
3752
                },
5✔
3753
            )
5✔
3754
            .await;
5✔
3755
            assert_eq!(network, rpc_server.network(context::current()).await?);
5✔
3756
        }
1✔
3757

1✔
3758
        Ok(())
1✔
3759
    }
1✔
3760

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

1✔
3767
        let network = Network::Main;
1✔
3768
        let mut rng = StdRng::seed_from_u64(123456789088u64);
1✔
3769

1✔
3770
        let rpc_server = test_rpc_server(
1✔
3771
            network,
1✔
3772
            WalletEntropy::new_pseudorandom(rng.random()),
1✔
3773
            2,
1✔
3774
            cli_args::Args::default(),
1✔
3775
        )
1✔
3776
        .await;
1✔
3777
        let token = cookie_token(&rpc_server).await;
1✔
3778
        let ctx = context::current();
1✔
3779
        let _ = rpc_server.clone().network(ctx).await;
1✔
3780
        let _ = rpc_server
1✔
3781
            .clone()
1✔
3782
            .own_listen_address_for_peers(ctx, token)
1✔
3783
            .await;
1✔
3784
        let _ = rpc_server.clone().own_instance_id(ctx, token).await;
1✔
3785
        let _ = rpc_server.clone().block_height(ctx, token).await;
1✔
3786
        let _ = rpc_server.clone().peer_info(ctx, token).await;
1✔
3787
        let _ = rpc_server
1✔
3788
            .clone()
1✔
3789
            .block_digests_by_height(ctx, token, 42u64.into())
1✔
3790
            .await;
1✔
3791
        let _ = rpc_server
1✔
3792
            .clone()
1✔
3793
            .block_digests_by_height(ctx, token, 0u64.into())
1✔
3794
            .await;
1✔
3795
        let _ = rpc_server.clone().all_punished_peers(ctx, token).await;
1✔
3796
        let _ = rpc_server.clone().latest_tip_digests(ctx, token, 2).await;
1✔
3797
        let _ = rpc_server
1✔
3798
            .clone()
1✔
3799
            .header(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3800
            .await;
1✔
3801
        let _ = rpc_server
1✔
3802
            .clone()
1✔
3803
            .block_info(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3804
            .await;
1✔
3805
        let _ = rpc_server
1✔
3806
            .clone()
1✔
3807
            .public_announcements_in_block(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3808
            .await;
1✔
3809
        let _ = rpc_server
1✔
3810
            .clone()
1✔
3811
            .block_digest(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
3812
            .await;
1✔
3813
        let _ = rpc_server.clone().utxo_digest(ctx, token, 0).await;
1✔
3814
        let _ = rpc_server
1✔
3815
            .clone()
1✔
3816
            .confirmed_available_balance(ctx, token)
1✔
3817
            .await;
1✔
3818
        let _ = rpc_server.clone().history(ctx, token).await;
1✔
3819
        let _ = rpc_server.clone().wallet_status(ctx, token).await;
1✔
3820
        let own_receiving_address = rpc_server
1✔
3821
            .clone()
1✔
3822
            .next_receiving_address(ctx, token, KeyType::Generation)
1✔
3823
            .await?
1✔
3824
            .unwrap();
1✔
3825
        let _ = rpc_server.clone().mempool_tx_count(ctx, token).await;
1✔
3826
        let _ = rpc_server.clone().mempool_size(ctx, token).await;
1✔
3827
        let _ = rpc_server.clone().dashboard_overview_data(ctx, token).await;
1✔
3828
        let _ = rpc_server
1✔
3829
            .clone()
1✔
3830
            .validate_address(
1✔
3831
                ctx,
1✔
3832
                token,
1✔
3833
                "Not a valid address".to_owned(),
1✔
3834
                Network::Testnet,
1✔
3835
            )
1✔
3836
            .await;
1✔
3837
        let _ = rpc_server.clone().pow_puzzle_internal_key(ctx, token).await;
1✔
3838
        let _ = rpc_server
1✔
3839
            .clone()
1✔
3840
            .pow_puzzle_external_key(ctx, token, rng.random())
1✔
3841
            .await;
1✔
3842
        let _ = rpc_server
1✔
3843
            .clone()
1✔
3844
            .provide_pow_solution(ctx, token, rng.random(), rng.random())
1✔
3845
            .await;
1✔
3846
        let _ = rpc_server
1✔
3847
            .clone()
1✔
3848
            .block_intervals(ctx, token, BlockSelector::Tip, None)
1✔
3849
            .await;
1✔
3850
        let _ = rpc_server
1✔
3851
            .clone()
1✔
3852
            .block_difficulties(ctx, token, BlockSelector::Tip, None)
1✔
3853
            .await;
1✔
3854
        let _ = rpc_server
1✔
3855
            .clone()
1✔
3856
            .broadcast_all_mempool_txs(ctx, token)
1✔
3857
            .await;
1✔
3858
        let _ = rpc_server.clone().mempool_overview(ctx, token, 0, 20).await;
1✔
3859
        let _ = rpc_server.clone().clear_all_standings(ctx, token).await;
1✔
3860
        let _ = rpc_server
1✔
3861
            .clone()
1✔
3862
            .clear_standing_by_ip(ctx, token, "127.0.0.1".parse().unwrap())
1✔
3863
            .await;
1✔
3864
        let _ = rpc_server
1✔
3865
            .clone()
1✔
3866
            .send(
1✔
3867
                ctx,
1✔
3868
                token,
1✔
3869
                NativeCurrencyAmount::one(),
1✔
3870
                own_receiving_address.clone(),
1✔
3871
                UtxoNotificationMedium::OffChain,
1✔
3872
                UtxoNotificationMedium::OffChain,
1✔
3873
                NativeCurrencyAmount::one(),
1✔
3874
            )
1✔
3875
            .await;
1✔
3876

1✔
3877
        let transaction_timestamp = network.launch_date();
1✔
3878
        let proving_capability = rpc_server.state.cli().proving_capability();
1✔
3879
        let _ = rpc_server
1✔
3880
            .clone()
1✔
3881
            .send_to_many_inner(
1✔
3882
                ctx,
1✔
3883
                vec![(own_receiving_address, NativeCurrencyAmount::one())],
1✔
3884
                (
1✔
3885
                    UtxoNotificationMedium::OffChain,
1✔
3886
                    UtxoNotificationMedium::OffChain,
1✔
3887
                ),
1✔
3888
                NativeCurrencyAmount::one(),
1✔
3889
                transaction_timestamp,
1✔
3890
                proving_capability,
1✔
3891
            )
1✔
3892
            .await;
1✔
3893
        let _ = rpc_server.clone().pause_miner(ctx, token).await;
1✔
3894
        let _ = rpc_server.clone().restart_miner(ctx, token).await;
1✔
3895
        let _ = rpc_server
1✔
3896
            .clone()
1✔
3897
            .prune_abandoned_monitored_utxos(ctx, token)
1✔
3898
            .await;
1✔
3899
        let _ = rpc_server.shutdown(ctx, token).await;
1✔
3900

1✔
3901
        Ok(())
1✔
3902
    }
1✔
3903

3904
    #[traced_test]
×
3905
    #[tokio::test]
3906
    async fn balance_is_zero_at_init() -> Result<()> {
1✔
3907
        // Verify that a wallet not receiving a premine is empty at startup
1✔
3908
        let rpc_server = test_rpc_server(
1✔
3909
            Network::Alpha,
1✔
3910
            WalletEntropy::new_random(),
1✔
3911
            2,
1✔
3912
            cli_args::Args::default(),
1✔
3913
        )
1✔
3914
        .await;
1✔
3915
        let token = cookie_token(&rpc_server).await;
1✔
3916
        let balance = rpc_server
1✔
3917
            .confirmed_available_balance(context::current(), token)
1✔
3918
            .await?;
1✔
3919
        assert!(balance.is_zero());
1✔
3920

1✔
3921
        Ok(())
1✔
3922
    }
1✔
3923

3924
    #[expect(clippy::shadow_unrelated)]
3925
    #[traced_test]
×
3926
    #[tokio::test]
3927
    async fn clear_ip_standing_test() -> Result<()> {
1✔
3928
        let mut rpc_server = test_rpc_server(
1✔
3929
            Network::Alpha,
1✔
3930
            WalletEntropy::new_random(),
1✔
3931
            2,
1✔
3932
            cli_args::Args::default(),
1✔
3933
        )
1✔
3934
        .await;
1✔
3935
        let token = cookie_token(&rpc_server).await;
1✔
3936
        let rpc_request_context = context::current();
1✔
3937
        let (peer_address0, peer_address1) = {
1✔
3938
            let global_state = rpc_server.state.lock_guard().await;
1✔
3939

1✔
3940
            (
1✔
3941
                global_state.net.peer_map.values().collect::<Vec<_>>()[0].connected_address(),
1✔
3942
                global_state.net.peer_map.values().collect::<Vec<_>>()[1].connected_address(),
1✔
3943
            )
1✔
3944
        };
1✔
3945

1✔
3946
        // Verify that sanctions list is empty
1✔
3947
        let punished_peers_startup = rpc_server
1✔
3948
            .clone()
1✔
3949
            .all_punished_peers(rpc_request_context, token)
1✔
3950
            .await?;
1✔
3951
        assert!(
1✔
3952
            punished_peers_startup.is_empty(),
1✔
3953
            "Sanctions list must be empty at startup"
1✔
3954
        );
1✔
3955

1✔
3956
        // sanction both
1✔
3957
        let (standing0, standing1) = {
1✔
3958
            let mut global_state_mut = rpc_server.state.lock_guard_mut().await;
1✔
3959

1✔
3960
            global_state_mut
1✔
3961
                .net
1✔
3962
                .peer_map
1✔
3963
                .entry(peer_address0)
1✔
3964
                .and_modify(|p| {
1✔
3965
                    p.standing
1✔
3966
                        .sanction(PeerSanction::Negative(
1✔
3967
                            NegativePeerSanction::DifferentGenesis,
1✔
3968
                        ))
1✔
3969
                        .unwrap_err();
1✔
3970
                });
1✔
3971
            global_state_mut
1✔
3972
                .net
1✔
3973
                .peer_map
1✔
3974
                .entry(peer_address1)
1✔
3975
                .and_modify(|p| {
1✔
3976
                    p.standing
1✔
3977
                        .sanction(PeerSanction::Negative(
1✔
3978
                            NegativePeerSanction::DifferentGenesis,
1✔
3979
                        ))
1✔
3980
                        .unwrap_err();
1✔
3981
                });
1✔
3982
            let standing_0 = global_state_mut.net.peer_map[&peer_address0].standing;
1✔
3983
            let standing_1 = global_state_mut.net.peer_map[&peer_address1].standing;
1✔
3984
            (standing_0, standing_1)
1✔
3985
        };
1✔
3986

1✔
3987
        // Verify expected sanctions reading
1✔
3988
        let punished_peers_from_memory = rpc_server
1✔
3989
            .clone()
1✔
3990
            .all_punished_peers(rpc_request_context, token)
1✔
3991
            .await?;
1✔
3992
        assert_eq!(
1✔
3993
            2,
1✔
3994
            punished_peers_from_memory.len(),
1✔
3995
            "Punished list must have two elements after sanctionings"
1✔
3996
        );
1✔
3997

1✔
3998
        {
1✔
3999
            let mut global_state_mut = rpc_server.state.lock_guard_mut().await;
1✔
4000

1✔
4001
            global_state_mut
1✔
4002
                .net
1✔
4003
                .write_peer_standing_on_decrease(peer_address0.ip(), standing0)
1✔
4004
                .await;
1✔
4005
            global_state_mut
1✔
4006
                .net
1✔
4007
                .write_peer_standing_on_decrease(peer_address1.ip(), standing1)
1✔
4008
                .await;
1✔
4009
        }
1✔
4010

1✔
4011
        // Verify expected sanctions reading, after DB-write
1✔
4012
        let punished_peers_from_memory_and_db = rpc_server
1✔
4013
            .clone()
1✔
4014
            .all_punished_peers(rpc_request_context, token)
1✔
4015
            .await?;
1✔
4016
        assert_eq!(
1✔
4017
            2,
1✔
4018
            punished_peers_from_memory_and_db.len(),
1✔
4019
            "Punished list must have to elements after sanctionings and after DB write"
1✔
4020
        );
1✔
4021

1✔
4022
        // Verify expected initial conditions
1✔
4023
        {
1✔
4024
            let global_state = rpc_server.state.lock_guard().await;
1✔
4025
            let standing0 = global_state
1✔
4026
                .net
1✔
4027
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4028
                .await;
1✔
4029
            assert_ne!(0, standing0.unwrap().standing);
1✔
4030
            assert_ne!(None, standing0.unwrap().latest_punishment);
1✔
4031
            let peer_standing_1 = global_state
1✔
4032
                .net
1✔
4033
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4034
                .await;
1✔
4035
            assert_ne!(0, peer_standing_1.unwrap().standing);
1✔
4036
            assert_ne!(None, peer_standing_1.unwrap().latest_punishment);
1✔
4037
            drop(global_state);
1✔
4038

1✔
4039
            // Clear standing of #0
1✔
4040
            rpc_server
1✔
4041
                .clone()
1✔
4042
                .clear_standing_by_ip(rpc_request_context, token, peer_address0.ip())
1✔
4043
                .await?;
1✔
4044
        }
1✔
4045

1✔
4046
        // Verify expected resulting conditions in database
1✔
4047
        {
1✔
4048
            let global_state = rpc_server.state.lock_guard().await;
1✔
4049
            let standing0 = global_state
1✔
4050
                .net
1✔
4051
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4052
                .await;
1✔
4053
            assert_eq!(0, standing0.unwrap().standing);
1✔
4054
            assert_eq!(None, standing0.unwrap().latest_punishment);
1✔
4055
            let standing1 = global_state
1✔
4056
                .net
1✔
4057
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4058
                .await;
1✔
4059
            assert_ne!(0, standing1.unwrap().standing);
1✔
4060
            assert_ne!(None, standing1.unwrap().latest_punishment);
1✔
4061

1✔
4062
            // Verify expected resulting conditions in peer map
1✔
4063
            let standing0_from_memory = global_state.net.peer_map[&peer_address0].clone();
1✔
4064
            assert_eq!(0, standing0_from_memory.standing.standing);
1✔
4065
            let standing1_from_memory = global_state.net.peer_map[&peer_address1].clone();
1✔
4066
            assert_ne!(0, standing1_from_memory.standing.standing);
1✔
4067
        }
1✔
4068

1✔
4069
        // Verify expected sanctions reading, after one forgiveness
1✔
4070
        let punished_list_after_one_clear = rpc_server
1✔
4071
            .clone()
1✔
4072
            .all_punished_peers(rpc_request_context, token)
1✔
4073
            .await?;
1✔
4074
        assert!(
1✔
4075
            punished_list_after_one_clear.len().is_one(),
1✔
4076
            "Punished list must have to elements after sanctionings and after DB write"
1✔
4077
        );
1✔
4078

1✔
4079
        Ok(())
1✔
4080
    }
1✔
4081

4082
    #[expect(clippy::shadow_unrelated)]
4083
    #[traced_test]
×
4084
    #[tokio::test]
4085
    async fn clear_all_standings_test() -> Result<()> {
1✔
4086
        // Create initial conditions
1✔
4087
        let mut rpc_server = test_rpc_server(
1✔
4088
            Network::Alpha,
1✔
4089
            WalletEntropy::new_random(),
1✔
4090
            2,
1✔
4091
            cli_args::Args::default(),
1✔
4092
        )
1✔
4093
        .await;
1✔
4094
        let token = cookie_token(&rpc_server).await;
1✔
4095
        let mut state = rpc_server.state.lock_guard_mut().await;
1✔
4096
        let peer_address0 = state.net.peer_map.values().collect::<Vec<_>>()[0].connected_address();
1✔
4097
        let peer_address1 = state.net.peer_map.values().collect::<Vec<_>>()[1].connected_address();
1✔
4098

1✔
4099
        // sanction both peers
1✔
4100
        let (standing0, standing1) = {
1✔
4101
            state.net.peer_map.entry(peer_address0).and_modify(|p| {
1✔
4102
                p.standing
1✔
4103
                    .sanction(PeerSanction::Negative(
1✔
4104
                        NegativePeerSanction::DifferentGenesis,
1✔
4105
                    ))
1✔
4106
                    .unwrap_err();
1✔
4107
            });
1✔
4108
            state.net.peer_map.entry(peer_address1).and_modify(|p| {
1✔
4109
                p.standing
1✔
4110
                    .sanction(PeerSanction::Negative(
1✔
4111
                        NegativePeerSanction::DifferentGenesis,
1✔
4112
                    ))
1✔
4113
                    .unwrap_err();
1✔
4114
            });
1✔
4115
            (
1✔
4116
                state.net.peer_map[&peer_address0].standing,
1✔
4117
                state.net.peer_map[&peer_address1].standing,
1✔
4118
            )
1✔
4119
        };
1✔
4120

1✔
4121
        state
1✔
4122
            .net
1✔
4123
            .write_peer_standing_on_decrease(peer_address0.ip(), standing0)
1✔
4124
            .await;
1✔
4125
        state
1✔
4126
            .net
1✔
4127
            .write_peer_standing_on_decrease(peer_address1.ip(), standing1)
1✔
4128
            .await;
1✔
4129

1✔
4130
        drop(state);
1✔
4131

1✔
4132
        // Verify expected initial conditions
1✔
4133
        {
1✔
4134
            let peer_standing0 = rpc_server
1✔
4135
                .state
1✔
4136
                .lock_guard_mut()
1✔
4137
                .await
1✔
4138
                .net
1✔
4139
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4140
                .await;
1✔
4141
            assert_ne!(0, peer_standing0.unwrap().standing);
1✔
4142
            assert_ne!(None, peer_standing0.unwrap().latest_punishment);
1✔
4143
        }
1✔
4144

1✔
4145
        {
1✔
4146
            let peer_standing1 = rpc_server
1✔
4147
                .state
1✔
4148
                .lock_guard_mut()
1✔
4149
                .await
1✔
4150
                .net
1✔
4151
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4152
                .await;
1✔
4153
            assert_ne!(0, peer_standing1.unwrap().standing);
1✔
4154
            assert_ne!(None, peer_standing1.unwrap().latest_punishment);
1✔
4155
        }
1✔
4156

1✔
4157
        // Verify expected reading through an RPC call
1✔
4158
        let rpc_request_context = context::current();
1✔
4159
        let after_two_sanctions = rpc_server
1✔
4160
            .clone()
1✔
4161
            .all_punished_peers(rpc_request_context, token)
1✔
4162
            .await?;
1✔
4163
        assert_eq!(2, after_two_sanctions.len());
1✔
4164

1✔
4165
        // Clear standing of both by clearing all standings
1✔
4166
        rpc_server
1✔
4167
            .clone()
1✔
4168
            .clear_all_standings(rpc_request_context, token)
1✔
4169
            .await?;
1✔
4170

1✔
4171
        let state = rpc_server.state.lock_guard().await;
1✔
4172

1✔
4173
        // Verify expected resulting conditions in database
1✔
4174
        {
1✔
4175
            let peer_standing_0 = state
1✔
4176
                .net
1✔
4177
                .get_peer_standing_from_database(peer_address0.ip())
1✔
4178
                .await;
1✔
4179
            assert_eq!(0, peer_standing_0.unwrap().standing);
1✔
4180
            assert_eq!(None, peer_standing_0.unwrap().latest_punishment);
1✔
4181
        }
1✔
4182

1✔
4183
        {
1✔
4184
            let peer_still_standing_1 = state
1✔
4185
                .net
1✔
4186
                .get_peer_standing_from_database(peer_address1.ip())
1✔
4187
                .await;
1✔
4188
            assert_eq!(0, peer_still_standing_1.unwrap().standing);
1✔
4189
            assert_eq!(None, peer_still_standing_1.unwrap().latest_punishment);
1✔
4190
        }
1✔
4191

1✔
4192
        // Verify expected resulting conditions in peer map
1✔
4193
        {
1✔
4194
            let peer_standing_0_from_memory = state.net.peer_map[&peer_address0].clone();
1✔
4195
            assert_eq!(0, peer_standing_0_from_memory.standing.standing);
1✔
4196
        }
1✔
4197

1✔
4198
        {
1✔
4199
            let peer_still_standing_1_from_memory = state.net.peer_map[&peer_address1].clone();
1✔
4200
            assert_eq!(0, peer_still_standing_1_from_memory.standing.standing);
1✔
4201
        }
1✔
4202

1✔
4203
        // Verify expected reading through an RPC call
1✔
4204
        let after_global_forgiveness = rpc_server
1✔
4205
            .clone()
1✔
4206
            .all_punished_peers(rpc_request_context, token)
1✔
4207
            .await?;
1✔
4208
        assert!(after_global_forgiveness.is_empty());
1✔
4209

1✔
4210
        Ok(())
1✔
4211
    }
1✔
4212

4213
    #[traced_test]
×
4214
    #[tokio::test]
4215
    async fn utxo_digest_test() {
1✔
4216
        let rpc_server = test_rpc_server(
1✔
4217
            Network::Alpha,
1✔
4218
            WalletEntropy::new_random(),
1✔
4219
            2,
1✔
4220
            cli_args::Args::default(),
1✔
4221
        )
1✔
4222
        .await;
1✔
4223
        let token = cookie_token(&rpc_server).await;
1✔
4224
        let aocl_leaves = rpc_server
1✔
4225
            .state
1✔
4226
            .lock_guard()
1✔
4227
            .await
1✔
4228
            .chain
1✔
4229
            .archival_state()
1✔
4230
            .archival_mutator_set
1✔
4231
            .ams()
1✔
4232
            .aocl
1✔
4233
            .num_leafs()
1✔
4234
            .await;
1✔
4235

1✔
4236
        debug_assert!(aocl_leaves > 0);
1✔
4237

1✔
4238
        assert!(rpc_server
1✔
4239
            .clone()
1✔
4240
            .utxo_digest(context::current(), token, aocl_leaves - 1)
1✔
4241
            .await
1✔
4242
            .unwrap()
1✔
4243
            .is_some());
1✔
4244

1✔
4245
        assert!(rpc_server
1✔
4246
            .utxo_digest(context::current(), token, aocl_leaves)
1✔
4247
            .await
1✔
4248
            .unwrap()
1✔
4249
            .is_none());
1✔
4250
    }
1✔
4251

4252
    #[traced_test]
×
4253
    #[tokio::test]
4254
    async fn block_info_test() {
1✔
4255
        let network = Network::RegTest;
1✔
4256
        let rpc_server = test_rpc_server(
1✔
4257
            network,
1✔
4258
            WalletEntropy::new_random(),
1✔
4259
            2,
1✔
4260
            cli_args::Args::default(),
1✔
4261
        )
1✔
4262
        .await;
1✔
4263
        let token = cookie_token(&rpc_server).await;
1✔
4264
        let global_state = rpc_server.state.lock_guard().await;
1✔
4265
        let ctx = context::current();
1✔
4266

1✔
4267
        let genesis_hash = global_state.chain.archival_state().genesis_block().hash();
1✔
4268
        let tip_hash = global_state.chain.light_state().hash();
1✔
4269

1✔
4270
        let genesis_block_info = BlockInfo::new(
1✔
4271
            global_state.chain.archival_state().genesis_block(),
1✔
4272
            genesis_hash,
1✔
4273
            tip_hash,
1✔
4274
            vec![],
1✔
4275
            global_state
1✔
4276
                .chain
1✔
4277
                .archival_state()
1✔
4278
                .block_belongs_to_canonical_chain(genesis_hash)
1✔
4279
                .await,
1✔
4280
        );
1✔
4281

1✔
4282
        assert!(
1✔
4283
            genesis_block_info.num_public_announcements.is_zero(),
1✔
4284
            "Genesis block contains no public announcements. Block info must reflect that."
1✔
4285
        );
1✔
4286

1✔
4287
        let tip_block_info = BlockInfo::new(
1✔
4288
            global_state.chain.light_state(),
1✔
4289
            genesis_hash,
1✔
4290
            tip_hash,
1✔
4291
            vec![],
1✔
4292
            global_state
1✔
4293
                .chain
1✔
4294
                .archival_state()
1✔
4295
                .block_belongs_to_canonical_chain(tip_hash)
1✔
4296
                .await,
1✔
4297
        );
1✔
4298

1✔
4299
        // should find genesis block by Genesis selector
1✔
4300
        assert_eq!(
1✔
4301
            genesis_block_info,
1✔
4302
            rpc_server
1✔
4303
                .clone()
1✔
4304
                .block_info(ctx, token, BlockSelector::Genesis)
1✔
4305
                .await
1✔
4306
                .unwrap()
1✔
4307
                .unwrap()
1✔
4308
        );
1✔
4309

1✔
4310
        // should find latest/tip block by Tip selector
1✔
4311
        assert_eq!(
1✔
4312
            tip_block_info,
1✔
4313
            rpc_server
1✔
4314
                .clone()
1✔
4315
                .block_info(ctx, token, BlockSelector::Tip)
1✔
4316
                .await
1✔
4317
                .unwrap()
1✔
4318
                .unwrap()
1✔
4319
        );
1✔
4320

1✔
4321
        // should find genesis block by Height selector
1✔
4322
        assert_eq!(
1✔
4323
            genesis_block_info,
1✔
4324
            rpc_server
1✔
4325
                .clone()
1✔
4326
                .block_info(ctx, token, BlockSelector::Height(BlockHeight::from(0u64)))
1✔
4327
                .await
1✔
4328
                .unwrap()
1✔
4329
                .unwrap()
1✔
4330
        );
1✔
4331

1✔
4332
        // should find genesis block by Digest selector
1✔
4333
        assert_eq!(
1✔
4334
            genesis_block_info,
1✔
4335
            rpc_server
1✔
4336
                .clone()
1✔
4337
                .block_info(ctx, token, BlockSelector::Digest(genesis_hash))
1✔
4338
                .await
1✔
4339
                .unwrap()
1✔
4340
                .unwrap()
1✔
4341
        );
1✔
4342

1✔
4343
        // should not find any block when Height selector is u64::Max
1✔
4344
        assert!(rpc_server
1✔
4345
            .clone()
1✔
4346
            .block_info(
1✔
4347
                ctx,
1✔
4348
                token,
1✔
4349
                BlockSelector::Height(BlockHeight::from(u64::MAX))
1✔
4350
            )
1✔
4351
            .await
1✔
4352
            .unwrap()
1✔
4353
            .is_none());
1✔
4354

1✔
4355
        // should not find any block when Digest selector is Digest::default()
1✔
4356
        assert!(rpc_server
1✔
4357
            .clone()
1✔
4358
            .block_info(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
4359
            .await
1✔
4360
            .unwrap()
1✔
4361
            .is_none());
1✔
4362
    }
1✔
4363

4364
    #[traced_test]
×
4365
    #[tokio::test]
4366
    async fn public_announcements_in_block_test() {
1✔
4367
        let network = Network::Main;
1✔
4368
        let mut rpc_server = test_rpc_server(
1✔
4369
            network,
1✔
4370
            WalletEntropy::new_random(),
1✔
4371
            2,
1✔
4372
            cli_args::Args::default(),
1✔
4373
        )
1✔
4374
        .await;
1✔
4375
        let mut rng = rand::rng();
1✔
4376
        let num_public_announcements_block1 = 7;
1✔
4377
        let num_inputs = 0;
1✔
4378
        let num_outputs = 2;
1✔
4379
        let tx_block1 = pseudorandom_transaction_kernel(
1✔
4380
            rng.random(),
1✔
4381
            num_inputs,
1✔
4382
            num_outputs,
1✔
4383
            num_public_announcements_block1,
1✔
4384
        );
1✔
4385
        let tx_block1 = Transaction {
1✔
4386
            kernel: tx_block1,
1✔
4387
            proof: TransactionProof::invalid(),
1✔
4388
        };
1✔
4389
        let block1 = invalid_block_with_transaction(&Block::genesis(network), tx_block1);
1✔
4390
        rpc_server.state.set_new_tip(block1.clone()).await.unwrap();
1✔
4391

1✔
4392
        let token = cookie_token(&rpc_server).await;
1✔
4393
        let ctx = context::current();
1✔
4394
        let block1_public_announcements = rpc_server
1✔
4395
            .clone()
1✔
4396
            .public_announcements_in_block(ctx, token, BlockSelector::Height(1u64.into()))
1✔
4397
            .await
1✔
4398
            .unwrap()
1✔
4399
            .unwrap();
1✔
4400
        assert_eq!(
1✔
4401
            block1.body().transaction_kernel.public_announcements,
1✔
4402
            block1_public_announcements,
1✔
4403
            "Must return expected public announcements"
1✔
4404
        );
1✔
4405
        assert_eq!(
1✔
4406
            num_public_announcements_block1,
1✔
4407
            block1_public_announcements.len(),
1✔
4408
            "Must return expected number of public announcements"
1✔
4409
        );
1✔
4410

1✔
4411
        let genesis_block_public_announcements = rpc_server
1✔
4412
            .clone()
1✔
4413
            .public_announcements_in_block(ctx, token, BlockSelector::Height(0u64.into()))
1✔
4414
            .await
1✔
4415
            .unwrap()
1✔
4416
            .unwrap();
1✔
4417
        assert!(
1✔
4418
            genesis_block_public_announcements.is_empty(),
1✔
4419
            "Genesis block has no public announements"
1✔
4420
        );
1✔
4421

1✔
4422
        assert!(
1✔
4423
            rpc_server
1✔
4424
                .public_announcements_in_block(ctx, token, BlockSelector::Height(2u64.into()))
1✔
4425
                .await
1✔
4426
                .unwrap()
1✔
4427
                .is_none(),
1✔
4428
            "Public announcements in unknown block must return None"
1✔
4429
        );
1✔
4430
    }
1✔
4431

4432
    #[traced_test]
×
4433
    #[tokio::test]
4434
    async fn block_digest_test() {
1✔
4435
        let network = Network::RegTest;
1✔
4436
        let rpc_server = test_rpc_server(
1✔
4437
            network,
1✔
4438
            WalletEntropy::new_random(),
1✔
4439
            2,
1✔
4440
            cli_args::Args::default(),
1✔
4441
        )
1✔
4442
        .await;
1✔
4443
        let token = cookie_token(&rpc_server).await;
1✔
4444
        let global_state = rpc_server.state.lock_guard().await;
1✔
4445
        let ctx = context::current();
1✔
4446

1✔
4447
        let genesis_hash = Block::genesis(network).hash();
1✔
4448

1✔
4449
        // should find genesis block by Genesis selector
1✔
4450
        assert_eq!(
1✔
4451
            genesis_hash,
1✔
4452
            rpc_server
1✔
4453
                .clone()
1✔
4454
                .block_digest(ctx, token, BlockSelector::Genesis)
1✔
4455
                .await
1✔
4456
                .unwrap()
1✔
4457
                .unwrap()
1✔
4458
        );
1✔
4459

1✔
4460
        // should find latest/tip block by Tip selector
1✔
4461
        assert_eq!(
1✔
4462
            global_state.chain.light_state().hash(),
1✔
4463
            rpc_server
1✔
4464
                .clone()
1✔
4465
                .block_digest(ctx, token, BlockSelector::Tip)
1✔
4466
                .await
1✔
4467
                .unwrap()
1✔
4468
                .unwrap()
1✔
4469
        );
1✔
4470

1✔
4471
        // should find genesis block by Height selector
1✔
4472
        assert_eq!(
1✔
4473
            genesis_hash,
1✔
4474
            rpc_server
1✔
4475
                .clone()
1✔
4476
                .block_digest(ctx, token, BlockSelector::Height(BlockHeight::from(0u64)))
1✔
4477
                .await
1✔
4478
                .unwrap()
1✔
4479
                .unwrap()
1✔
4480
        );
1✔
4481

1✔
4482
        // should find genesis block by Digest selector
1✔
4483
        assert_eq!(
1✔
4484
            genesis_hash,
1✔
4485
            rpc_server
1✔
4486
                .clone()
1✔
4487
                .block_digest(ctx, token, BlockSelector::Digest(genesis_hash))
1✔
4488
                .await
1✔
4489
                .unwrap()
1✔
4490
                .unwrap()
1✔
4491
        );
1✔
4492

1✔
4493
        // should not find any block when Height selector is u64::Max
1✔
4494
        assert!(rpc_server
1✔
4495
            .clone()
1✔
4496
            .block_digest(
1✔
4497
                ctx,
1✔
4498
                token,
1✔
4499
                BlockSelector::Height(BlockHeight::from(u64::MAX))
1✔
4500
            )
1✔
4501
            .await
1✔
4502
            .unwrap()
1✔
4503
            .is_none());
1✔
4504

1✔
4505
        // should not find any block when Digest selector is Digest::default()
1✔
4506
        assert!(rpc_server
1✔
4507
            .clone()
1✔
4508
            .block_digest(ctx, token, BlockSelector::Digest(Digest::default()))
1✔
4509
            .await
1✔
4510
            .unwrap()
1✔
4511
            .is_none());
1✔
4512
    }
1✔
4513

4514
    #[traced_test]
×
4515
    #[tokio::test]
4516
    async fn getting_temperature_doesnt_crash_test() {
1✔
4517
        // On your local machine, this should return a temperature but in CI,
1✔
4518
        // the RPC call returns `None`, so we only verify that the call doesn't
1✔
4519
        // crash the host machine, we don't verify that any value is returned.
1✔
4520
        let rpc_server = test_rpc_server(
1✔
4521
            Network::Alpha,
1✔
4522
            WalletEntropy::new_random(),
1✔
4523
            2,
1✔
4524
            cli_args::Args::default(),
1✔
4525
        )
1✔
4526
        .await;
1✔
4527
        let token = cookie_token(&rpc_server).await;
1✔
4528
        let _current_server_temperature = rpc_server
1✔
4529
            .cpu_temp(context::current(), token)
1✔
4530
            .await
1✔
4531
            .unwrap();
1✔
4532
    }
1✔
4533

4534
    #[traced_test]
×
4535
    #[tokio::test]
4536
    async fn cannot_initiate_transaction_if_notx_flag_is_set() {
1✔
4537
        let network = Network::Main;
1✔
4538
        let ctx = context::current();
1✔
4539
        let mut rng = rand::rng();
1✔
4540
        let address = GenerationSpendingKey::derive_from_seed(rng.random()).to_address();
1✔
4541
        let amount = NativeCurrencyAmount::coins(rng.random_range(0..10));
1✔
4542

1✔
4543
        // set flag on, verify non-initiation
1✔
4544
        let cli_on = cli_args::Args {
1✔
4545
            no_transaction_initiation: true,
1✔
4546
            ..Default::default()
1✔
4547
        };
1✔
4548

1✔
4549
        let rpc_server = test_rpc_server(network, WalletEntropy::new_random(), 2, cli_on).await;
1✔
4550
        let token = cookie_token(&rpc_server).await;
1✔
4551

1✔
4552
        assert!(rpc_server
1✔
4553
            .clone()
1✔
4554
            .send(
1✔
4555
                ctx,
1✔
4556
                token,
1✔
4557
                amount,
1✔
4558
                address.into(),
1✔
4559
                UtxoNotificationMedium::OffChain,
1✔
4560
                UtxoNotificationMedium::OffChain,
1✔
4561
                NativeCurrencyAmount::zero()
1✔
4562
            )
1✔
4563
            .await
1✔
4564
            .is_err());
1✔
4565
        assert!(rpc_server
1✔
4566
            .clone()
1✔
4567
            .send_to_many(
1✔
4568
                ctx,
1✔
4569
                token,
1✔
4570
                vec![(address.into(), amount)],
1✔
4571
                UtxoNotificationMedium::OffChain,
1✔
4572
                UtxoNotificationMedium::OffChain,
1✔
4573
                NativeCurrencyAmount::zero()
1✔
4574
            )
1✔
4575
            .await
1✔
4576
            .is_err());
1✔
4577
    }
1✔
4578

4579
    mod pow_puzzle_tests {
4580
        use rand::random;
4581
        use tasm_lib::twenty_first::math::other::random_elements;
4582

4583
        use super::*;
4584
        use crate::mine_loop::fast_kernel_mast_hash;
4585
        use crate::models::state::block_proposal::BlockProposal;
4586
        use crate::models::state::wallet::address::hash_lock_key::HashLockKey;
4587
        use crate::tests::shared::invalid_empty_block;
4588

4589
        #[test]
4590
        fn pow_puzzle_is_consistent_with_block_hash() {
1✔
4591
            let network = Network::Main;
1✔
4592
            let genesis = Block::genesis(network);
1✔
4593
            let mut block1 = invalid_empty_block(&genesis);
1✔
4594
            let hash_lock_key = HashLockKey::from_preimage(random());
1✔
4595
            block1.set_header_guesser_digest(hash_lock_key.after_image());
1✔
4596

1✔
4597
            let guess_challenge = ProofOfWorkPuzzle::new(block1.clone(), *genesis.header());
1✔
4598
            assert_eq!(guess_challenge.prev_block, genesis.hash());
1✔
4599

4600
            let nonce = random();
1✔
4601
            let resulting_block_hash = fast_kernel_mast_hash(
1✔
4602
                guess_challenge.kernel_auth_path,
1✔
4603
                guess_challenge.header_auth_path,
1✔
4604
                nonce,
1✔
4605
            );
1✔
4606

1✔
4607
            block1.set_header_nonce(nonce);
1✔
4608

1✔
4609
            assert_eq!(block1.hash(), resulting_block_hash);
1✔
4610
        }
1✔
4611

4612
        #[tokio::test]
4613
        async fn provide_solution_when_no_proposal_known() {
1✔
4614
            let network = Network::Main;
1✔
4615
            let bob = test_rpc_server(
1✔
4616
                network,
1✔
4617
                WalletEntropy::new_random(),
1✔
4618
                2,
1✔
4619
                cli_args::Args::default(),
1✔
4620
            )
1✔
4621
            .await;
1✔
4622
            let bob_token = cookie_token(&bob).await;
1✔
4623
            assert!(!bob
1✔
4624
                .state
1✔
4625
                .lock_guard()
1✔
4626
                .await
1✔
4627
                .mining_state
1✔
4628
                .block_proposal
1✔
4629
                .is_some());
1✔
4630
            let accepted = bob
1✔
4631
                .clone()
1✔
4632
                .provide_pow_solution(context::current(), bob_token, random(), random())
1✔
4633
                .await
1✔
4634
                .unwrap();
1✔
4635
            assert!(
1✔
4636
                !accepted,
1✔
4637
                "Must reject PoW solution when no proposal exists"
1✔
4638
            );
1✔
4639
        }
1✔
4640

4641
        #[tokio::test]
4642
        async fn cached_exported_proposals_are_stored_correctly() {
1✔
4643
            let network = Network::Main;
1✔
4644
            let bob = WalletEntropy::new_random();
1✔
4645
            let mut bob = test_rpc_server(network, bob.clone(), 2, cli_args::Args::default()).await;
1✔
4646

1✔
4647
            let genesis = Block::genesis(network);
1✔
4648
            let block1 = invalid_empty_block(&genesis);
1✔
4649
            bob.state
1✔
4650
                .lock_mut(|x| {
1✔
4651
                    x.mining_state.block_proposal =
1✔
4652
                        BlockProposal::ForeignComposition(block1.clone())
1✔
4653
                })
1✔
4654
                .await;
1✔
4655
            let bob_token = cookie_token(&bob).await;
1✔
4656

1✔
4657
            let num_exported_block_proposals = 6;
1✔
4658
            let guesser_digests = random_elements(6);
1✔
4659
            let mut pow_puzzle_ids = vec![];
1✔
4660
            for guesser_digest in guesser_digests.clone() {
6✔
4661
                let pow_puzzle = bob
6✔
4662
                    .clone()
6✔
4663
                    .pow_puzzle_external_key(context::current(), bob_token, guesser_digest)
6✔
4664
                    .await
6✔
4665
                    .unwrap()
6✔
4666
                    .unwrap();
6✔
4667
                assert!(!pow_puzzle_ids.contains(&pow_puzzle.id));
6✔
4668
                pow_puzzle_ids.push(pow_puzzle.id);
6✔
4669
            }
1✔
4670

1✔
4671
            assert_eq!(
1✔
4672
                num_exported_block_proposals,
1✔
4673
                bob.state
1✔
4674
                    .lock_guard()
1✔
4675
                    .await
1✔
4676
                    .mining_state
1✔
4677
                    .exported_block_proposals
1✔
4678
                    .len()
1✔
4679
            );
1✔
4680

1✔
4681
            // Verify that the same exported puzzle is not added twice.
1✔
4682
            for guesser_digest in guesser_digests {
7✔
4683
                bob.clone()
6✔
4684
                    .pow_puzzle_external_key(context::current(), bob_token, guesser_digest)
6✔
4685
                    .await
6✔
4686
                    .unwrap()
6✔
4687
                    .unwrap();
6✔
4688
            }
1✔
4689
            assert_eq!(
1✔
4690
                num_exported_block_proposals,
1✔
4691
                bob.state
1✔
4692
                    .lock_guard()
1✔
4693
                    .await
1✔
4694
                    .mining_state
1✔
4695
                    .exported_block_proposals
1✔
4696
                    .len()
1✔
4697
            );
1✔
4698
        }
1✔
4699

4700
        #[tokio::test]
4701
        async fn exported_pow_puzzle_is_consistent_with_block_hash() {
1✔
4702
            let network = Network::Main;
1✔
4703
            let bob = WalletEntropy::new_random();
1✔
4704
            let mut bob = test_rpc_server(network, bob.clone(), 2, cli_args::Args::default()).await;
1✔
4705
            let bob_token = cookie_token(&bob).await;
1✔
4706

1✔
4707
            let genesis = Block::genesis(network);
1✔
4708
            let mut block1 = invalid_empty_block(&genesis);
1✔
4709
            bob.state
1✔
4710
                .lock_mut(|x| {
1✔
4711
                    x.mining_state.block_proposal =
1✔
4712
                        BlockProposal::ForeignComposition(block1.clone())
1✔
4713
                })
1✔
4714
                .await;
1✔
4715

1✔
4716
            let external_key = WalletEntropy::new_random();
1✔
4717
            let external_guesser_key = external_key.guesser_spending_key(genesis.hash());
1✔
4718
            let external_guesser_digest = external_guesser_key.after_image();
1✔
4719
            let internal_guesser_digest = bob
1✔
4720
                .state
1✔
4721
                .lock(|x| {
1✔
4722
                    x.wallet_state
1✔
4723
                        .wallet_entropy
1✔
4724
                        .guesser_spending_key(genesis.hash())
1✔
4725
                })
1✔
4726
                .await
1✔
4727
                .after_image();
1✔
4728

1✔
4729
            for use_internal_key in [true, false] {
3✔
4730
                println!("use_internal_key: {use_internal_key}");
2✔
4731
                let pow_puzzle = if use_internal_key {
2✔
4732
                    bob.clone()
1✔
4733
                        .pow_puzzle_internal_key(context::current(), bob_token)
1✔
4734
                        .await
1✔
4735
                        .unwrap()
1✔
4736
                        .unwrap()
1✔
4737
                } else {
1✔
4738
                    bob.clone()
1✔
4739
                        .pow_puzzle_external_key(
1✔
4740
                            context::current(),
1✔
4741
                            bob_token,
1✔
4742
                            external_guesser_digest,
1✔
4743
                        )
1✔
4744
                        .await
1✔
4745
                        .unwrap()
1✔
4746
                        .unwrap()
1✔
4747
                };
1✔
4748

1✔
4749
                let guesser_digest = if use_internal_key {
2✔
4750
                    internal_guesser_digest
1✔
4751
                } else {
1✔
4752
                    external_guesser_digest
1✔
4753
                };
1✔
4754

1✔
4755
                assert!(
2✔
4756
                    bob.state
2✔
4757
                        .lock_guard()
2✔
4758
                        .await
2✔
4759
                        .mining_state
1✔
4760
                        .exported_block_proposals
1✔
4761
                        .contains_key(&pow_puzzle.id),
2✔
4762
                    "Must have stored exported block proposal"
1✔
4763
                );
1✔
4764

1✔
4765
                let mock_nonce = random();
2✔
4766
                let mut resulting_block_hash = fast_kernel_mast_hash(
2✔
4767
                    pow_puzzle.kernel_auth_path,
2✔
4768
                    pow_puzzle.header_auth_path,
2✔
4769
                    mock_nonce,
2✔
4770
                );
2✔
4771

2✔
4772
                block1.set_header_nonce(mock_nonce);
2✔
4773
                block1.set_header_guesser_digest(guesser_digest);
2✔
4774
                assert_eq!(block1.hash(), resulting_block_hash);
2✔
4775
                assert_eq!(
2✔
4776
                    block1.total_guesser_reward(),
2✔
4777
                    pow_puzzle.total_guesser_reward
2✔
4778
                );
2✔
4779

1✔
4780
                // Check that succesful guess is accepted by endpoint.
1✔
4781
                let actual_threshold = genesis.header().difficulty.target();
2✔
4782
                let mut actual_nonce = mock_nonce;
2✔
4783
                while resulting_block_hash > actual_threshold {
25,680✔
4784
                    actual_nonce = random();
25,678✔
4785
                    resulting_block_hash = fast_kernel_mast_hash(
25,678✔
4786
                        pow_puzzle.kernel_auth_path,
25,678✔
4787
                        pow_puzzle.header_auth_path,
25,678✔
4788
                        actual_nonce,
25,678✔
4789
                    );
25,678✔
4790
                }
25,678✔
4791

1✔
4792
                block1.set_header_nonce(actual_nonce);
2✔
4793
                let good_is_accepted = bob
2✔
4794
                    .clone()
2✔
4795
                    .provide_pow_solution(
2✔
4796
                        context::current(),
2✔
4797
                        bob_token,
2✔
4798
                        actual_nonce,
2✔
4799
                        pow_puzzle.id,
2✔
4800
                    )
2✔
4801
                    .await
2✔
4802
                    .unwrap();
2✔
4803
                assert!(
2✔
4804
                    good_is_accepted,
2✔
4805
                    "Actual PoW-puzzle solution must be accepted by RPC endpoint."
1✔
4806
                );
1✔
4807

1✔
4808
                // Check that bad guess is rejected by endpoint.
1✔
4809
                let mut bad_nonce: Digest = actual_nonce;
2✔
4810
                while resulting_block_hash <= actual_threshold {
4✔
4811
                    bad_nonce = random();
2✔
4812
                    resulting_block_hash = fast_kernel_mast_hash(
2✔
4813
                        pow_puzzle.kernel_auth_path,
2✔
4814
                        pow_puzzle.header_auth_path,
2✔
4815
                        bad_nonce,
2✔
4816
                    );
2✔
4817
                }
2✔
4818
                let bad_is_accepted = bob
2✔
4819
                    .clone()
2✔
4820
                    .provide_pow_solution(context::current(), bob_token, bad_nonce, pow_puzzle.id)
2✔
4821
                    .await
2✔
4822
                    .unwrap();
2✔
4823
                assert!(
2✔
4824
                    !bad_is_accepted,
2✔
4825
                    "Bad PoW solution must be rejected by RPC endpoint."
1✔
4826
                );
1✔
4827
            }
1✔
4828
        }
1✔
4829
    }
4830

4831
    mod claim_utxo_tests {
4832
        use super::*;
4833

4834
        #[traced_test]
×
4835
        #[tokio::test]
4836
        async fn claim_utxo_owned_before_confirmed() -> Result<()> {
1✔
4837
            worker::claim_utxo_owned(false, false).await
1✔
4838
        }
1✔
4839

4840
        #[traced_test]
×
4841
        #[tokio::test]
4842
        async fn claim_utxo_owned_after_confirmed() -> Result<()> {
1✔
4843
            worker::claim_utxo_owned(true, false).await
1✔
4844
        }
1✔
4845

4846
        #[traced_test]
×
4847
        #[tokio::test]
4848
        async fn claim_utxo_owned_after_confirmed_and_after_spent() -> Result<()> {
1✔
4849
            worker::claim_utxo_owned(true, true).await
1✔
4850
        }
1✔
4851

4852
        #[traced_test]
×
4853
        #[tokio::test]
4854
        async fn claim_utxo_unowned_before_confirmed() -> Result<()> {
1✔
4855
            worker::claim_utxo_unowned(false).await
1✔
4856
        }
1✔
4857

4858
        #[traced_test]
×
4859
        #[tokio::test]
4860
        async fn claim_utxo_unowned_after_confirmed() -> Result<()> {
1✔
4861
            worker::claim_utxo_unowned(true).await
1✔
4862
        }
1✔
4863

4864
        mod worker {
4865
            use cli_args::Args;
4866

4867
            use super::*;
4868
            use crate::tests::shared::invalid_block_with_transaction;
4869
            use crate::tests::shared::invalid_empty_block;
4870

4871
            pub(super) async fn claim_utxo_unowned(claim_after_confirmed: bool) -> Result<()> {
2✔
4872
                let network = Network::Main;
2✔
4873

4874
                // bob's node
4875
                let (pay_to_bob_outputs, bob_rpc_server, bob_token) = {
2✔
4876
                    let rpc_server =
2✔
4877
                        test_rpc_server(network, WalletEntropy::new_random(), 2, Args::default())
2✔
4878
                            .await;
2✔
4879
                    let token = cookie_token(&rpc_server).await;
2✔
4880

4881
                    let receiving_address_generation = rpc_server
2✔
4882
                        .clone()
2✔
4883
                        .next_receiving_address(context::current(), token, KeyType::Generation)
2✔
4884
                        .await?
2✔
4885
                        .unwrap();
2✔
4886
                    let receiving_address_symmetric = rpc_server
2✔
4887
                        .clone()
2✔
4888
                        .next_receiving_address(context::current(), token, KeyType::Symmetric)
2✔
4889
                        .await?
2✔
4890
                        .unwrap();
2✔
4891

2✔
4892
                    let pay_to_bob_outputs = vec![
2✔
4893
                        (receiving_address_generation, NativeCurrencyAmount::coins(1)),
2✔
4894
                        (receiving_address_symmetric, NativeCurrencyAmount::coins(2)),
2✔
4895
                    ];
2✔
4896

2✔
4897
                    (pay_to_bob_outputs, rpc_server, token)
2✔
4898
                };
4899

4900
                // alice's node
4901
                let (blocks, alice_to_bob_utxo_notifications, bob_amount) = {
2✔
4902
                    let wallet_entropy = WalletEntropy::new_random();
2✔
4903
                    let mut rpc_server =
2✔
4904
                        test_rpc_server(network, wallet_entropy.clone(), 2, Args::default()).await;
2✔
4905

4906
                    let genesis_block = Block::genesis(network);
2✔
4907
                    let mut blocks = vec![];
2✔
4908
                    let in_seven_months = genesis_block.header().timestamp + Timestamp::months(7);
2✔
4909

2✔
4910
                    let fee = NativeCurrencyAmount::zero();
2✔
4911
                    let bob_amount: NativeCurrencyAmount =
2✔
4912
                        pay_to_bob_outputs.iter().map(|(_, amt)| *amt).sum();
4✔
4913

2✔
4914
                    // Mine block 1 to get some coins
2✔
4915

2✔
4916
                    let cb_key = wallet_entropy.nth_generation_spending_key(0);
2✔
4917
                    let (block1, composer_expected_utxos) =
2✔
4918
                        make_mock_block(&genesis_block, None, cb_key, Default::default()).await;
2✔
4919
                    blocks.push(block1.clone());
2✔
4920

2✔
4921
                    rpc_server
2✔
4922
                        .state
2✔
4923
                        .set_new_self_composed_tip(block1.clone(), composer_expected_utxos)
2✔
4924
                        .await
2✔
4925
                        .unwrap();
2✔
4926

4927
                    let (tx, offchain_notifications) = rpc_server
2✔
4928
                        .clone()
2✔
4929
                        .send_to_many_inner_invalid_proof(
2✔
4930
                            pay_to_bob_outputs,
2✔
4931
                            UtxoNotificationMedium::OffChain,
2✔
4932
                            UtxoNotificationMedium::OffChain,
2✔
4933
                            fee,
2✔
4934
                            in_seven_months,
2✔
4935
                        )
2✔
4936
                        .await
2✔
4937
                        .unwrap();
2✔
4938

2✔
4939
                    let block2 = invalid_block_with_transaction(&block1, tx);
2✔
4940
                    let block3 = invalid_empty_block(&block2);
2✔
4941

2✔
4942
                    // mine two blocks, the first will include the transaction
2✔
4943
                    blocks.push(block2);
2✔
4944
                    blocks.push(block3);
2✔
4945

2✔
4946
                    (blocks, offchain_notifications, bob_amount)
2✔
4947
                };
2✔
4948

2✔
4949
                // bob's node claims each utxo
2✔
4950
                {
2✔
4951
                    let mut state = bob_rpc_server.state.clone();
2✔
4952

2✔
4953
                    state.set_new_tip(blocks[0].clone()).await?;
2✔
4954

4955
                    if claim_after_confirmed {
2✔
4956
                        state.set_new_tip(blocks[1].clone()).await?;
1✔
4957
                        state.set_new_tip(blocks[2].clone()).await?;
1✔
4958
                    }
1✔
4959

4960
                    for utxo_notification in alice_to_bob_utxo_notifications {
6✔
4961
                        // Register the same UTXO multiple times to ensure that this does not
4962
                        // change the balance.
4963
                        let claim_was_new0 = bob_rpc_server
4✔
4964
                            .clone()
4✔
4965
                            .claim_utxo(
4✔
4966
                                context::current(),
4✔
4967
                                bob_token,
4✔
4968
                                utxo_notification.ciphertext.clone(),
4✔
4969
                                None,
4✔
4970
                            )
4✔
4971
                            .await
4✔
4972
                            .unwrap();
4✔
4973
                        assert!(claim_was_new0);
4✔
4974
                        let claim_was_new1 = bob_rpc_server
4✔
4975
                            .clone()
4✔
4976
                            .claim_utxo(
4✔
4977
                                context::current(),
4✔
4978
                                bob_token,
4✔
4979
                                utxo_notification.ciphertext,
4✔
4980
                                None,
4✔
4981
                            )
4✔
4982
                            .await
4✔
4983
                            .unwrap();
4✔
4984
                        assert!(!claim_was_new1);
4✔
4985
                    }
4986

4987
                    assert_eq!(
2✔
4988
                        vec![
2✔
4989
                            NativeCurrencyAmount::coins(1), // claimed via generation addr
2✔
4990
                            NativeCurrencyAmount::coins(2), // claimed via symmetric addr
2✔
4991
                        ],
2✔
4992
                        state
2✔
4993
                            .lock_guard()
2✔
4994
                            .await
2✔
4995
                            .wallet_state
4996
                            .wallet_db
4997
                            .expected_utxos()
2✔
4998
                            .get_all()
2✔
4999
                            .await
2✔
5000
                            .iter()
2✔
5001
                            .map(|eu| eu.utxo.get_native_currency_amount())
4✔
5002
                            .collect_vec()
2✔
5003
                    );
5004

5005
                    if !claim_after_confirmed {
2✔
5006
                        assert_eq!(
1✔
5007
                            NativeCurrencyAmount::zero(),
1✔
5008
                            bob_rpc_server
1✔
5009
                                .clone()
1✔
5010
                                .confirmed_available_balance(context::current(), bob_token)
1✔
5011
                                .await?,
1✔
5012
                        );
5013
                        state.set_new_tip(blocks[1].clone()).await?;
1✔
5014
                        state.set_new_tip(blocks[2].clone()).await?;
1✔
5015
                    }
1✔
5016

5017
                    assert_eq!(
2✔
5018
                        bob_amount,
2✔
5019
                        bob_rpc_server
2✔
5020
                            .confirmed_available_balance(context::current(), bob_token)
2✔
5021
                            .await?,
2✔
5022
                    );
5023
                }
5024

5025
                Ok(())
2✔
5026
            }
2✔
5027

5028
            pub(super) async fn claim_utxo_owned(
3✔
5029
                claim_after_mined: bool,
3✔
5030
                spent: bool,
3✔
5031
            ) -> Result<()> {
3✔
5032
                assert!(
3✔
5033
                    !spent || claim_after_mined,
3✔
5034
                    "If UTXO is spent, it must also be mined"
×
5035
                );
5036
                let network = Network::Main;
3✔
5037
                let bob_wallet = WalletEntropy::new_random();
3✔
5038
                let mut bob =
3✔
5039
                    test_rpc_server(network, bob_wallet.clone(), 2, Args::default()).await;
3✔
5040
                let bob_token = cookie_token(&bob).await;
3✔
5041

5042
                let in_seven_months =
3✔
5043
                    Block::genesis(network).header().timestamp + Timestamp::months(7);
3✔
5044
                let in_eight_months = in_seven_months + Timestamp::months(1);
3✔
5045

3✔
5046
                let bob_key = bob_wallet.nth_generation_spending_key(0);
3✔
5047
                let genesis_block = Block::genesis(network);
3✔
5048
                let (block1, composer_expected_utxos) =
3✔
5049
                    make_mock_block(&genesis_block, None, bob_key, Default::default()).await;
3✔
5050

5051
                bob.state
3✔
5052
                    .set_new_self_composed_tip(block1.clone(), composer_expected_utxos)
3✔
5053
                    .await
3✔
5054
                    .unwrap();
3✔
5055

5056
                let bob_gen_addr = bob
3✔
5057
                    .clone()
3✔
5058
                    .next_receiving_address(context::current(), bob_token, KeyType::Generation)
3✔
5059
                    .await?
3✔
5060
                    .unwrap();
3✔
5061
                let bob_sym_addr = bob
3✔
5062
                    .clone()
3✔
5063
                    .next_receiving_address(context::current(), bob_token, KeyType::Symmetric)
3✔
5064
                    .await?
3✔
5065
                    .unwrap();
3✔
5066

3✔
5067
                let pay_to_self_outputs = vec![
3✔
5068
                    (bob_gen_addr, NativeCurrencyAmount::coins(5)),
3✔
5069
                    (bob_sym_addr, NativeCurrencyAmount::coins(6)),
3✔
5070
                ];
3✔
5071

3✔
5072
                let fee = NativeCurrencyAmount::coins(2);
3✔
5073
                let (tx, offchain_notifications) = bob
3✔
5074
                    .clone()
3✔
5075
                    .send_to_many_inner_invalid_proof(
3✔
5076
                        pay_to_self_outputs.clone(),
3✔
5077
                        UtxoNotificationMedium::OffChain,
3✔
5078
                        UtxoNotificationMedium::OffChain,
3✔
5079
                        fee,
3✔
5080
                        in_eight_months,
3✔
5081
                    )
3✔
5082
                    .await
3✔
5083
                    .unwrap();
3✔
5084

3✔
5085
                // alice mines 2 more blocks.  block2 confirms the sent tx.
3✔
5086
                let block2 = invalid_block_with_transaction(&block1, tx);
3✔
5087
                let block3 = invalid_empty_block(&block2);
3✔
5088

3✔
5089
                if claim_after_mined {
3✔
5090
                    // bob applies the blocks before claiming utxos.
5091
                    bob.state.set_new_tip(block2.clone()).await?;
2✔
5092
                    bob.state.set_new_tip(block3.clone()).await?;
2✔
5093

5094
                    if spent {
2✔
5095
                        // Send entire liquid balance somewhere else
5096
                        let another_address = WalletEntropy::new_random()
1✔
5097
                            .nth_generation_spending_key(0)
1✔
5098
                            .to_address();
1✔
5099
                        let (spending_tx, _) = bob
1✔
5100
                            .clone()
1✔
5101
                            .send_to_many_inner_invalid_proof(
1✔
5102
                                vec![(another_address.into(), NativeCurrencyAmount::coins(62))],
1✔
5103
                                UtxoNotificationMedium::OffChain,
1✔
5104
                                UtxoNotificationMedium::OffChain,
1✔
5105
                                NativeCurrencyAmount::zero(),
1✔
5106
                                in_eight_months,
1✔
5107
                            )
1✔
5108
                            .await
1✔
5109
                            .unwrap();
1✔
5110
                        let block4 = invalid_block_with_transaction(&block3, spending_tx);
1✔
5111
                        bob.state.set_new_tip(block4.clone()).await?;
1✔
5112
                    }
1✔
5113
                }
1✔
5114

5115
                for offchain_notification in offchain_notifications {
9✔
5116
                    bob.clone()
6✔
5117
                        .claim_utxo(
6✔
5118
                            context::current(),
6✔
5119
                            bob_token,
6✔
5120
                            offchain_notification.ciphertext,
6✔
5121
                            None,
6✔
5122
                        )
6✔
5123
                        .await?;
6✔
5124
                }
5125

5126
                assert_eq!(
3✔
5127
                    vec![
3✔
5128
                        NativeCurrencyAmount::coins(64), // liquid composer reward, block 1
3✔
5129
                        NativeCurrencyAmount::coins(64), // illiquid composer reward, block 1
3✔
5130
                        NativeCurrencyAmount::coins(5),  // claimed via generation addr
3✔
5131
                        NativeCurrencyAmount::coins(6),  // claimed via symmetric addr
3✔
5132
                        // 51 = (64 - 5 - 6 - 2 (fee))
3✔
5133
                        NativeCurrencyAmount::coins(51) // change (symmetric addr)
3✔
5134
                    ],
3✔
5135
                    bob.state
3✔
5136
                        .lock_guard()
3✔
5137
                        .await
3✔
5138
                        .wallet_state
5139
                        .wallet_db
5140
                        .expected_utxos()
3✔
5141
                        .get_all()
3✔
5142
                        .await
3✔
5143
                        .iter()
3✔
5144
                        .map(|eu| eu.utxo.get_native_currency_amount())
15✔
5145
                        .collect_vec()
3✔
5146
                );
5147

5148
                if !claim_after_mined {
3✔
5149
                    // bob hasn't applied blocks 2,3. liquid balance should be 64
5150
                    assert_eq!(
1✔
5151
                        NativeCurrencyAmount::coins(64),
1✔
5152
                        bob.clone()
1✔
5153
                            .confirmed_available_balance(context::current(), bob_token)
1✔
5154
                            .await?,
1✔
5155
                    );
5156
                    // bob applies the blocks after claiming utxos.
5157
                    bob.state.set_new_tip(block2).await?;
1✔
5158
                    bob.state.set_new_tip(block3).await?;
1✔
5159
                }
2✔
5160

5161
                if spent {
3✔
5162
                    assert!(bob
1✔
5163
                        .confirmed_available_balance(context::current(), bob_token)
1✔
5164
                        .await?
1✔
5165
                        .is_zero(),);
1✔
5166
                } else {
5167
                    // final liquid balance should be 62.
5168
                    // +64 composer liquid
5169
                    // +64 composer timelocked (not counted)
5170
                    // -64 composer liquid spent
5171
                    // +5 self-send via Generation
5172
                    // +6 self-send via Symmetric
5173
                    // +51   change (less fee == 2)
5174
                    assert_eq!(
2✔
5175
                        NativeCurrencyAmount::coins(62),
2✔
5176
                        bob.confirmed_available_balance(context::current(), bob_token)
2✔
5177
                            .await?,
2✔
5178
                    );
5179
                }
5180
                Ok(())
3✔
5181
            }
3✔
5182
        }
5183
    }
5184

5185
    mod send_tests {
5186
        use super::*;
5187

5188
        #[traced_test]
×
5189
        #[tokio::test]
5190
        async fn send_to_many_n_outputs() {
1✔
5191
            let mut rng = StdRng::seed_from_u64(1815);
1✔
5192
            let network = Network::Main;
1✔
5193
            let rpc_server = test_rpc_server(
1✔
5194
                network,
1✔
5195
                WalletEntropy::new_pseudorandom(rng.random()),
1✔
5196
                2,
1✔
5197
                cli_args::Args::default(),
1✔
5198
            )
1✔
5199
            .await;
1✔
5200
            let token = cookie_token(&rpc_server).await;
1✔
5201

1✔
5202
            let ctx = context::current();
1✔
5203
            let timestamp = network.launch_date() + Timestamp::days(1);
1✔
5204
            let own_address = rpc_server
1✔
5205
                .clone()
1✔
5206
                .next_receiving_address(ctx, token, KeyType::Generation)
1✔
5207
                .await
1✔
5208
                .unwrap()
1✔
5209
                .unwrap();
1✔
5210
            let elem = (own_address.clone(), NativeCurrencyAmount::zero());
1✔
5211
            let outputs = std::iter::repeat(elem);
1✔
5212
            let fee = NativeCurrencyAmount::zero();
1✔
5213

1✔
5214
            // note: we can only perform 2 iters, else we bump into send rate-limit (per block)
1✔
5215
            for i in 5..7 {
3✔
5216
                let result = rpc_server
2✔
5217
                    .clone()
2✔
5218
                    .send_to_many_inner(
2✔
5219
                        ctx,
2✔
5220
                        outputs.clone().take(i).collect(),
2✔
5221
                        (
2✔
5222
                            UtxoNotificationMedium::OffChain,
2✔
5223
                            UtxoNotificationMedium::OffChain,
2✔
5224
                        ),
2✔
5225
                        fee,
2✔
5226
                        timestamp,
2✔
5227
                        TxProvingCapability::PrimitiveWitness,
2✔
5228
                    )
2✔
5229
                    .await;
2✔
5230
                assert!(result.is_ok());
2✔
5231
            }
1✔
5232
        }
1✔
5233

5234
        /// sends a tx with two outputs: one self, one external, for each key type
5235
        /// that accepts incoming UTXOs.
5236
        #[traced_test]
×
5237
        #[tokio::test]
5238
        async fn send_to_many_test() -> Result<()> {
1✔
5239
            for recipient_key_type in KeyType::all_types_for_receiving() {
3✔
5240
                worker::send_to_many(recipient_key_type).await?;
2✔
5241
            }
1✔
5242
            Ok(())
1✔
5243
        }
1✔
5244

5245
        /// checks that the sending rate limit kicks in after 2 tx are sent.
5246
        /// note: rate-limit only applies below block 25000
5247
        #[traced_test]
×
5248
        #[tokio::test]
5249
        async fn send_rate_limit() -> Result<()> {
1✔
5250
            let mut rng = StdRng::seed_from_u64(1815);
1✔
5251
            let network = Network::Main;
1✔
5252
            let rpc_server = test_rpc_server(
1✔
5253
                network,
1✔
5254
                WalletEntropy::devnet_wallet(),
1✔
5255
                2,
1✔
5256
                cli_args::Args::default(),
1✔
5257
            )
1✔
5258
            .await;
1✔
5259

1✔
5260
            let ctx = context::current();
1✔
5261
            let timestamp = network.launch_date() + Timestamp::months(7);
1✔
5262

1✔
5263
            let address: ReceivingAddress = GenerationSpendingKey::derive_from_seed(rng.random())
1✔
5264
                .to_address()
1✔
5265
                .into();
1✔
5266
            let amount = NativeCurrencyAmount::coins(rng.random_range(0..10));
1✔
5267
            let fee = NativeCurrencyAmount::coins(1);
1✔
5268

1✔
5269
            let outputs = vec![(address, amount)];
1✔
5270

1✔
5271
            for i in 0..10 {
11✔
5272
                let result = rpc_server
10✔
5273
                    .clone()
10✔
5274
                    .send_to_many_inner(
10✔
5275
                        ctx,
10✔
5276
                        outputs.clone(),
10✔
5277
                        (
10✔
5278
                            UtxoNotificationMedium::OnChain,
10✔
5279
                            UtxoNotificationMedium::OnChain,
10✔
5280
                        ),
10✔
5281
                        fee,
10✔
5282
                        timestamp,
10✔
5283
                        TxProvingCapability::PrimitiveWitness,
10✔
5284
                    )
10✔
5285
                    .await;
10✔
5286

1✔
5287
                // any attempts after the 2nd send should result in RateLimit error.
1✔
5288
                match i {
10✔
5289
                    0..2 => assert!(result.is_ok()),
10✔
5290
                    _ => assert!(matches!(result, Err(error::SendError::RateLimit { .. }))),
8✔
5291
                }
1✔
5292
            }
1✔
5293

1✔
5294
            Ok(())
1✔
5295
        }
1✔
5296

5297
        mod worker {
5298
            use super::*;
5299
            use crate::models::state::wallet::address::generation_address::GenerationReceivingAddress;
5300
            use crate::models::state::wallet::address::symmetric_key::SymmetricKey;
5301

5302
            // sends a tx with two outputs: one self, one external.
5303
            //
5304
            // input: recipient_key_type: can be symmetric or generation.
5305
            //
5306
            // Steps:
5307
            // --- Init.  Basics ---
5308
            // --- Init.  get wallet spending key ---
5309
            // --- Init.  generate a block, with coinbase going to our wallet ---
5310
            // --- Init.  append the block to blockchain ---
5311
            // --- Setup. generate an output that our wallet cannot claim. ---
5312
            // --- Setup. generate an output that our wallet can claim. ---
5313
            // --- Setup. assemble outputs and fee ---
5314
            // --- Store: store num expected utxo before spend ---
5315
            // --- Operation: perform send_to_many
5316
            // --- Test: bech32m serialize/deserialize roundtrip.
5317
            // --- Test: verify op returns a value.
5318
            // --- Test: verify expected_utxos.len() has increased by 2.
5319
            pub(super) async fn send_to_many(recipient_key_type: KeyType) -> Result<()> {
2✔
5320
                info!("recipient_key_type: {}", recipient_key_type);
2✔
5321

5322
                // --- Init.  Basics ---
5323
                let mut rng = StdRng::seed_from_u64(1814);
2✔
5324
                let network = Network::Main;
2✔
5325
                let mut rpc_server = test_rpc_server(
2✔
5326
                    network,
2✔
5327
                    WalletEntropy::new_pseudorandom(rng.random()),
2✔
5328
                    2,
2✔
5329
                    cli_args::Args::default(),
2✔
5330
                )
2✔
5331
                .await;
2✔
5332
                let ctx = context::current();
2✔
5333

2✔
5334
                // --- Init.  get wallet spending key ---
2✔
5335
                let genesis_block = Block::genesis(network);
2✔
5336
                let wallet_spending_key = rpc_server
2✔
5337
                    .state
2✔
5338
                    .lock_guard_mut()
2✔
5339
                    .await
2✔
5340
                    .wallet_state
5341
                    .next_unused_spending_key(KeyType::Generation)
2✔
5342
                    .await
2✔
5343
                    .unwrap();
2✔
5344
                let SpendingKey::Generation(wallet_spending_key) = wallet_spending_key else {
2✔
5345
                    panic!("Expected generation key")
×
5346
                };
5347

5348
                // --- Init.  generate a block, with composer fee going to our
5349
                // wallet ---
5350
                let timestamp = network.launch_date() + Timestamp::days(1);
2✔
5351
                let (block_1, composer_utxos) = make_mock_block(
2✔
5352
                    &genesis_block,
2✔
5353
                    Some(timestamp),
2✔
5354
                    wallet_spending_key,
2✔
5355
                    rng.random(),
2✔
5356
                )
2✔
5357
                .await;
2✔
5358

5359
                {
5360
                    let state_lock = rpc_server.state.lock_guard().await;
2✔
5361
                    let wallet_status = state_lock.get_wallet_status_for_tip().await;
2✔
5362
                    let original_balance = state_lock
2✔
5363
                        .wallet_state
2✔
5364
                        .confirmed_available_balance(&wallet_status, timestamp);
2✔
5365
                    assert!(original_balance.is_zero(), "Original balance assumed zero");
2✔
5366
                };
5367

5368
                // --- Init.  append the block to blockchain ---
5369
                rpc_server
2✔
5370
                    .state
2✔
5371
                    .set_new_self_composed_tip(block_1.clone(), composer_utxos)
2✔
5372
                    .await?;
2✔
5373

5374
                {
5375
                    let state_lock = rpc_server.state.lock_guard().await;
2✔
5376
                    let wallet_status = state_lock.get_wallet_status_for_tip().await;
2✔
5377
                    let new_balance = state_lock
2✔
5378
                        .wallet_state
2✔
5379
                        .confirmed_available_balance(&wallet_status, timestamp);
2✔
5380
                    let mut expected_balance = Block::block_subsidy(block_1.header().height);
2✔
5381
                    expected_balance.div_two();
2✔
5382
                    assert_eq!(
2✔
5383
                        expected_balance, new_balance,
5384
                        "New balance must be exactly 1/2 mining reward bc timelock"
×
5385
                    );
5386
                };
5387

5388
                // --- Setup. generate an output that our wallet cannot claim. ---
5389
                let external_receiving_address: ReceivingAddress = match recipient_key_type {
2✔
5390
                    KeyType::Generation => {
5391
                        GenerationReceivingAddress::derive_from_seed(rng.random()).into()
1✔
5392
                    }
5393
                    KeyType::Symmetric => SymmetricKey::from_seed(rng.random()).into(),
1✔
5394
                    KeyType::RawHashLock => panic!("hash lock key not supported"),
×
5395
                };
5396
                let output1 = (
2✔
5397
                    external_receiving_address.clone(),
2✔
5398
                    NativeCurrencyAmount::coins(5),
2✔
5399
                );
2✔
5400

5401
                // --- Setup. generate an output that our wallet can claim. ---
5402
                let output2 = {
2✔
5403
                    let spending_key = rpc_server
2✔
5404
                        .state
2✔
5405
                        .lock_guard_mut()
2✔
5406
                        .await
2✔
5407
                        .wallet_state
5408
                        .next_unused_spending_key(recipient_key_type)
2✔
5409
                        .await
2✔
5410
                        .unwrap();
2✔
5411
                    (
2✔
5412
                        spending_key.to_address().unwrap(),
2✔
5413
                        NativeCurrencyAmount::coins(25),
2✔
5414
                    )
2✔
5415
                };
2✔
5416

2✔
5417
                // --- Setup. assemble outputs and fee ---
2✔
5418
                let outputs = vec![output1, output2];
2✔
5419
                let fee = NativeCurrencyAmount::coins(1);
2✔
5420

5421
                // --- Store: store num expected utxo before spend ---
5422
                let num_expected_utxo = rpc_server
2✔
5423
                    .state
2✔
5424
                    .lock_guard()
2✔
5425
                    .await
2✔
5426
                    .wallet_state
5427
                    .wallet_db
5428
                    .expected_utxos()
2✔
5429
                    .len()
2✔
5430
                    .await;
2✔
5431

5432
                // --- Operation: perform send_to_many
5433
                // It's important to call a method where you get to inject the
5434
                // timestamp. Otherwise, proofs cannot be reused, and CI will
5435
                // fail. CI might also fail if you don't set an explicit proving
5436
                // capability.
5437
                let result = rpc_server
2✔
5438
                    .clone()
2✔
5439
                    .send_to_many_inner(
2✔
5440
                        ctx,
2✔
5441
                        outputs,
2✔
5442
                        (
2✔
5443
                            UtxoNotificationMedium::OffChain,
2✔
5444
                            UtxoNotificationMedium::OffChain,
2✔
5445
                        ),
2✔
5446
                        fee,
2✔
5447
                        timestamp,
2✔
5448
                        TxProvingCapability::ProofCollection,
2✔
5449
                    )
2✔
5450
                    .await;
2✔
5451

5452
                // --- Test: bech32m serialize/deserialize roundtrip.
5453
                assert_eq!(
2✔
5454
                    external_receiving_address,
2✔
5455
                    ReceivingAddress::from_bech32m(
2✔
5456
                        &external_receiving_address.to_bech32m(network)?,
2✔
5457
                        network,
2✔
5458
                    )?
×
5459
                );
5460

5461
                // --- Test: verify op returns a value.
5462
                assert!(result.is_ok());
2✔
5463

5464
                // --- Test: verify expected_utxos.len() has increased by 2.
5465
                //           (one off-chain utxo + one change utxo)
5466
                assert_eq!(
2✔
5467
                    rpc_server
2✔
5468
                        .state
2✔
5469
                        .lock_guard()
2✔
5470
                        .await
2✔
5471
                        .wallet_state
5472
                        .wallet_db
5473
                        .expected_utxos()
2✔
5474
                        .len()
2✔
5475
                        .await,
2✔
5476
                    num_expected_utxo + 2
2✔
5477
                );
5478

5479
                Ok(())
2✔
5480
            }
2✔
5481
        }
5482
    }
5483
}
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