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

Neptune-Crypto / neptune-core / 14919393916

09 May 2025 01:23AM UTC coverage: 71.653% (-3.3%) from 74.963%
14919393916

push

github

dan-da
style: address review comments

Addresses review comments for #583:

+ use #[cfg(not(test))] instead of bool param to disable initial sleep
  in mining loop for unit tests
+ improve comment in mining loop when checking composer_task error
+ rename RpcError::Error to RpcError::WalletError
+ remove redundant copy_dir_recursive() in src/tests/shared.rs

0 of 4 new or added lines in 2 files covered. (0.0%)

172 existing lines in 18 files now uncovered.

19870 of 27731 relevant lines covered (71.65%)

367043.22 hits per line

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

66.26
/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
use std::sync::Arc;
50

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

67
use crate::api;
68
use crate::api::tx_initiation;
69
use crate::api::tx_initiation::builder::tx_input_list_builder::InputSelectionPolicy;
70
use crate::api::tx_initiation::builder::tx_output_list_builder::OutputFormat;
71
use crate::config_models::network::Network;
72
use crate::macros::fn_name;
73
use crate::macros::log_slow_scope;
74
use crate::mine_loop::precalculate_block_auth_paths;
75
use crate::models::blockchain::block::block_header::BlockHeader;
76
use crate::models::blockchain::block::block_height::BlockHeight;
77
use crate::models::blockchain::block::block_info::BlockInfo;
78
use crate::models::blockchain::block::block_kernel::BlockKernel;
79
use crate::models::blockchain::block::block_selector::BlockSelector;
80
use crate::models::blockchain::block::difficulty_control::Difficulty;
81
use crate::models::blockchain::block::Block;
82
use crate::models::blockchain::transaction::transaction_proof::TransactionProofType;
83
use crate::models::blockchain::transaction::PublicAnnouncement;
84
use crate::models::blockchain::transaction::Transaction;
85
use crate::models::blockchain::transaction::TransactionProof;
86
use crate::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
87
use crate::models::channel::ClaimUtxoData;
88
use crate::models::channel::RPCServerToMain;
89
use crate::models::peer::peer_info::PeerInfo;
90
use crate::models::peer::InstanceId;
91
use crate::models::peer::PeerStanding;
92
use crate::models::proof_abstractions::mast_hash::MastHash;
93
use crate::models::proof_abstractions::timestamp::Timestamp;
94
use crate::models::state::mining_state::MAX_NUM_EXPORTED_BLOCK_PROPOSAL_STORED;
95
use crate::models::state::mining_status::MiningStatus;
96
use crate::models::state::transaction_details::TransactionDetails;
97
use crate::models::state::transaction_kernel_id::TransactionKernelId;
98
use crate::models::state::tx_creation_artifacts::TxCreationArtifacts;
99
use crate::models::state::tx_proving_capability::TxProvingCapability;
100
use crate::models::state::wallet::address::encrypted_utxo_notification::EncryptedUtxoNotification;
101
use crate::models::state::wallet::address::BaseKeyType;
102
use crate::models::state::wallet::address::BaseSpendingKey;
103
use crate::models::state::wallet::address::KeyType;
104
use crate::models::state::wallet::address::ReceivingAddress;
105
use crate::models::state::wallet::change_policy::ChangePolicy;
106
use crate::models::state::wallet::coin_with_possible_timelock::CoinWithPossibleTimeLock;
107
use crate::models::state::wallet::expected_utxo::UtxoNotifier;
108
use crate::models::state::wallet::incoming_utxo::IncomingUtxo;
109
use crate::models::state::wallet::monitored_utxo::MonitoredUtxo;
110
use crate::models::state::wallet::transaction_input::TxInputList;
111
use crate::models::state::wallet::transaction_output::TxOutputList;
112
use crate::models::state::wallet::wallet_status::WalletStatus;
113
use crate::models::state::GlobalState;
114
use crate::models::state::GlobalStateLock;
115
use crate::prelude::twenty_first;
116
use crate::rpc_auth;
117
use crate::twenty_first::prelude::Tip5;
118
use crate::DataDirectory;
119

120
/// result returned by RPC methods
121
pub type RpcResult<T> = Result<T, error::RpcError>;
122

123
#[derive(Clone, Debug, Serialize, Deserialize)]
124
pub struct DashBoardOverviewDataFromClient {
125
    pub tip_digest: Digest,
126
    pub tip_header: BlockHeader,
127
    pub syncing: bool,
128
    pub confirmed_available_balance: NativeCurrencyAmount,
129
    pub confirmed_total_balance: NativeCurrencyAmount,
130
    pub unconfirmed_available_balance: NativeCurrencyAmount,
131
    pub unconfirmed_total_balance: NativeCurrencyAmount,
132
    pub mempool_size: usize,
133
    pub mempool_total_tx_count: usize,
134
    pub mempool_own_tx_count: usize,
135

136
    // `None` symbolizes failure in getting peer count
137
    pub peer_count: Option<usize>,
138
    pub max_num_peers: usize,
139

140
    // `None` symbolizes failure to get mining status
141
    pub mining_status: Option<MiningStatus>,
142

143
    pub proving_capability: TxProvingCapability,
144

145
    // # of confirmations of the last wallet balance change.
146
    //
147
    // Starts at 1, as the block in which a tx is included is considered the 1st
148
    // confirmation.
149
    //
150
    // `None` indicates that wallet balance has never changed.
151
    pub confirmations: Option<BlockHeight>,
152

153
    /// CPU temperature in degrees Celsius
154
    pub cpu_temp: Option<f32>,
155
}
156

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

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

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

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

205
    pub fn synced(mut self) -> Self {
×
206
        self.synced = true;
×
207
        self
×
208
    }
×
209
}
210

211
/// Data required to attempt to solve the proof-of-work puzzle that allows the
212
/// minting of the next block.
213
#[derive(Clone, Debug, Copy, Serialize, Deserialize)]
214
pub struct ProofOfWorkPuzzle {
215
    // All fields public since used downstream by mining pool software.
216
    pub kernel_auth_path: [Digest; BlockKernel::MAST_HEIGHT],
217
    pub header_auth_path: [Digest; BlockHeader::MAST_HEIGHT],
218

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

223
    /// The total reward, timelocked plus liquid, for a successful guess.
224
    pub total_guesser_reward: NativeCurrencyAmount,
225

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

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

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

246
        let id = Tip5::hash(&(kernel_auth_path, header_auth_path));
15✔
247

248
        Self {
15✔
249
            kernel_auth_path,
15✔
250
            header_auth_path,
15✔
251
            threshold,
15✔
252
            total_guesser_reward: guesser_reward,
15✔
253
            id,
15✔
254
            prev_block,
15✔
255
        }
15✔
256
    }
15✔
257
}
258

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

948
    /// generate a new receiving address of the specified type
949
    ///
950
    /// a payment recipient (payee) should call this method to obtain a
951
    /// an address which can be provided to the payment sender (payer).
952
    ///
953
    /// # important! read or risk losing funds!!!
954
    ///
955
    /// for most transactions, use [KeyType::Generation].
956
    ///
957
    /// [KeyType::Symmetric] must *only* be used if the payer and
958
    /// payee are the same party, ie the payer is sending to a wallet
959
    /// under their control.
960
    ///
961
    /// This is because when `KeyType::Symmetric` is specified the returned
962
    /// "address" is also the spending key.  Anyone who received this "address"
963
    /// can spend the funds.  So never give it out!
964
    ///
965
    /// `KeyType::Symmetric` is provided as an option for self-owned payments
966
    /// because it requires much less space on the blockchain, which can also
967
    /// potentially lessen fees.
968
    ///
969
    /// Note that by default `KeyType::Symmetric` is used for change outputs
970
    /// and block rewards.
971
    ///
972
    /// If in any doubt, just use [KeyType::Generation].
973
    ///
974
    /// ```no_run
975
    /// # use anyhow::Result;
976
    /// use neptune_cash::models::state::wallet::address::KeyType;
977
    /// # use neptune_cash::rpc_server::RPCClient;
978
    /// # use neptune_cash::rpc_auth;
979
    /// # use tarpc::tokio_serde::formats::Json;
980
    /// # use tarpc::serde_transport::tcp;
981
    /// # use tarpc::client;
982
    /// # use tarpc::context;
983
    /// #
984
    /// # #[tokio::main]
985
    /// # async fn main() -> Result<()>{
986
    /// #
987
    /// # // create a serde/json transport over tcp.
988
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
989
    /// #
990
    /// # // create an rpc client using the transport.
991
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
992
    /// #
993
    /// # // Defines cookie hint
994
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
995
    /// #
996
    /// # // load the cookie file from disk and assign it to a token
997
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
998
    /// #
999
    /// // set cryptographic key type for receiving funds
1000
    /// let key_type = KeyType::Generation;
1001
    ///
1002
    /// // query neptune-core server to get a receiving address
1003
    /// let wallet_status = client.next_receiving_address(context::current(), token, key_type).await??;
1004
    /// # Ok(())
1005
    /// # }
1006
    /// ```
1007
    async fn next_receiving_address(
1008
        token: rpc_auth::Token,
1009
        key_type: KeyType,
1010
    ) -> RpcResult<ReceivingAddress>;
1011

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

1045
    /// Return known keys for the provided [KeyType]
1046
    ///
1047
    /// ```no_run
1048
    /// # use anyhow::Result;
1049
    /// use neptune_cash::models::state::wallet::address::BaseKeyType;
1050
    /// # use neptune_cash::rpc_server::RPCClient;
1051
    /// # use neptune_cash::rpc_auth;
1052
    /// # use tarpc::tokio_serde::formats::Json;
1053
    /// # use tarpc::serde_transport::tcp;
1054
    /// # use tarpc::client;
1055
    /// # use tarpc::context;
1056
    /// #
1057
    /// # #[tokio::main]
1058
    /// # async fn main() -> Result<()>{
1059
    /// #
1060
    /// # // create a serde/json transport over tcp.
1061
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1062
    /// #
1063
    /// # // create an rpc client using the transport.
1064
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1065
    /// #
1066
    /// # // Defines cookie hint
1067
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1068
    /// #
1069
    /// # // load the cookie file from disk and assign it to a token
1070
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1071
    /// #
1072
    /// // set a key type
1073
    /// let key_type = BaseKeyType::Symmetric;
1074
    ///
1075
    /// // query neptune-core server to get all known keys by [KeyType]
1076
    /// let known_keys_by_keytype = client.known_keys_by_keytype(context::current(), token, key_type ).await??;
1077
    /// # Ok(())
1078
    /// # }
1079
    /// ```
1080
    async fn known_keys_by_keytype(
1081
        token: rpc_auth::Token,
1082
        key_type: BaseKeyType,
1083
    ) -> RpcResult<Vec<BaseSpendingKey>>;
1084

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

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

1121
    /// Return info about the transactions in the mempool
1122
    ///
1123
    /// ```no_run
1124
    /// # use anyhow::Result;
1125
    /// # use neptune_cash::rpc_server::RPCClient;
1126
    /// # use neptune_cash::rpc_auth;
1127
    /// # use tarpc::tokio_serde::formats::Json;
1128
    /// # use tarpc::serde_transport::tcp;
1129
    /// # use tarpc::client;
1130
    /// # use tarpc::context;
1131
    /// #
1132
    /// # #[tokio::main]
1133
    /// # async fn main() -> Result<()>{
1134
    /// #
1135
    /// # // create a serde/json transport over tcp.
1136
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1137
    /// #
1138
    /// # // create an rpc client using the transport.
1139
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1140
    /// #
1141
    /// # // Defines cookie hint
1142
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1143
    /// #
1144
    /// # // load the cookie file from disk and assign it to a token
1145
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1146
    /// #
1147
    /// // index to start from in the mempool
1148
    /// let start_index : usize = 37;
1149
    ///
1150
    /// // number of transactions
1151
    /// let number : usize = 8;
1152
    ///
1153
    /// // query neptune-core server to get the info of transactions in the mempool
1154
    /// let mempool_overview = client.mempool_overview(context::current(), token, start_index, number ).await??;
1155
    /// # Ok(())
1156
    /// # }
1157
    /// ```
1158
    async fn mempool_overview(
1159
        token: rpc_auth::Token,
1160
        start_index: usize,
1161
        number: usize,
1162
    ) -> RpcResult<Vec<MempoolTransactionInfo>>;
1163

1164
    /// Return the information used on the dashboard's overview tab
1165
    ///
1166
    /// ```no_run
1167
    /// # use anyhow::Result;
1168
    /// # use neptune_cash::rpc_server::RPCClient;
1169
    /// # use neptune_cash::rpc_auth;
1170
    /// # use tarpc::tokio_serde::formats::Json;
1171
    /// # use tarpc::serde_transport::tcp;
1172
    /// # use tarpc::client;
1173
    /// # use tarpc::context;
1174
    /// #
1175
    /// # #[tokio::main]
1176
    /// # async fn main() -> Result<()>{
1177
    /// #
1178
    /// # // create a serde/json transport over tcp.
1179
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1180
    /// #
1181
    /// # // create an rpc client using the transport.
1182
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1183
    /// #
1184
    /// # // Defines cookie hint
1185
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1186
    /// #
1187
    /// # // load the cookie file from disk and assign it to a token
1188
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1189
    /// #
1190
    /// // query neptune-core server to get the info used on dashboard overview tab
1191
    /// let dashboard_data = client.dashboard_overview_data(context::current(), token).await??;
1192
    /// # Ok(())
1193
    /// # }
1194
    /// ```
1195
    async fn dashboard_overview_data(
1196
        token: rpc_auth::Token,
1197
    ) -> RpcResult<DashBoardOverviewDataFromClient>;
1198

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

1243
    /// Determine whether the user-supplied string is a valid amount
1244
    ///
1245
    /// ```no_run
1246
    /// # use anyhow::Result;
1247
    /// # use neptune_cash::rpc_server::RPCClient;
1248
    /// # use neptune_cash::rpc_auth;
1249
    /// # use tarpc::tokio_serde::formats::Json;
1250
    /// # use tarpc::serde_transport::tcp;
1251
    /// # use tarpc::client;
1252
    /// # use tarpc::context;
1253
    /// #
1254
    /// # #[tokio::main]
1255
    /// # async fn main() -> Result<()>{
1256
    /// #
1257
    /// # // create a serde/json transport over tcp.
1258
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1259
    /// #
1260
    /// # // create an rpc client using the transport.
1261
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1262
    /// #
1263
    /// # // Defines cookie hint
1264
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1265
    /// #
1266
    /// # // load the cookie file from disk and assign it to a token
1267
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1268
    /// #
1269
    /// // address to validate
1270
    /// let amount : String = "132".to_string();
1271
    ///
1272
    /// // query neptune-core server to determine if the amount is valid
1273
    /// let is_valid_address = client.validate_amount(context::current(), token, amount ).await??;
1274
    /// # Ok(())
1275
    /// # }
1276
    /// ```
1277
    async fn validate_amount(
1278
        token: rpc_auth::Token,
1279
        amount: String,
1280
    ) -> RpcResult<Option<NativeCurrencyAmount>>;
1281

1282
    /// Determine whether the given amount is less than (or equal to) the balance
1283
    ///
1284
    /// ```no_run
1285
    /// # use anyhow::Result;
1286
    /// use neptune_cash::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
1287
    /// # use neptune_cash::rpc_server::RPCClient;
1288
    /// # use neptune_cash::rpc_auth;
1289
    /// # use tarpc::tokio_serde::formats::Json;
1290
    /// # use tarpc::serde_transport::tcp;
1291
    /// # use tarpc::client;
1292
    /// # use tarpc::context;
1293
    /// #
1294
    /// # #[tokio::main]
1295
    /// # async fn main() -> Result<()>{
1296
    /// #
1297
    /// # // create a serde/json transport over tcp.
1298
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1299
    /// #
1300
    /// # // create an rpc client using the transport.
1301
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1302
    /// #
1303
    /// # // Defines cookie hint
1304
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1305
    /// #
1306
    /// # // load the cookie file from disk and assign it to a token
1307
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1308
    /// #
1309
    /// // setting the amount to 47
1310
    /// let amount : NativeCurrencyAmount = NativeCurrencyAmount::coins(47);
1311
    ///
1312
    /// // query neptune-core server to determine if the amount is less than or equal to the balance
1313
    /// let amount_less_or_equals_balance = client.amount_leq_confirmed_available_balance(context::current(), token, amount ).await??;
1314
    /// # Ok(())
1315
    /// # }
1316
    /// ```
1317
    async fn amount_leq_confirmed_available_balance(
1318
        token: rpc_auth::Token,
1319
        amount: NativeCurrencyAmount,
1320
    ) -> RpcResult<bool>;
1321

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

1355
    /// Get CPU temperature.
1356
    ///
1357
    /// ```no_run
1358
    /// # use anyhow::Result;
1359
    /// # use neptune_cash::rpc_server::RPCClient;
1360
    /// # use neptune_cash::rpc_auth;
1361
    /// # use tarpc::tokio_serde::formats::Json;
1362
    /// # use tarpc::serde_transport::tcp;
1363
    /// # use tarpc::client;
1364
    /// # use tarpc::context;
1365
    /// #
1366
    /// # #[tokio::main]
1367
    /// # async fn main() -> Result<()>{
1368
    /// #
1369
    /// # // create a serde/json transport over tcp.
1370
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1371
    /// #
1372
    /// # // create an rpc client using the transport.
1373
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1374
    /// #
1375
    /// # // Defines cookie hint
1376
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1377
    /// #
1378
    /// # // load the cookie file from disk and assign it to a token
1379
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1380
    /// #
1381
    /// // query neptune-core server instance to get its CPU temperature
1382
    /// let cpu_temperature = client.cpu_temp(context::current(), token ).await??;
1383
    /// # Ok(())
1384
    /// # }
1385
    /// ```
1386
    async fn cpu_temp(token: rpc_auth::Token) -> RpcResult<Option<f32>>;
1387

1388
    /// Get the proof-of-work puzzle for the current block proposal. Uses the
1389
    /// node's secret key to populate the guesser digest.
1390
    ///
1391
    /// Returns `None` if no block proposal for the next block is known yet.
1392
    async fn pow_puzzle_internal_key(
1393
        token: rpc_auth::Token,
1394
    ) -> RpcResult<Option<ProofOfWorkPuzzle>>;
1395

1396
    /// Get the proof-of-work puzzle for the current block proposal. Like
1397
    /// [Self::pow_puzzle_internal_key] but returned puzzle uses an externally
1398
    /// provided digest to populate the guesser digest field in the block
1399
    /// header, meaning that this client cannot claim the reward in case a
1400
    /// valid PoW-solution is found. This endpoint allows for "cold" guessing
1401
    /// where the node does not hold the key to spend the guesser reward.
1402
    ///
1403
    /// Returns `None` if no block proposal for the next block is known yet.
1404
    async fn pow_puzzle_external_key(
1405
        token: rpc_auth::Token,
1406
        guesser_digest: Digest,
1407
    ) -> RpcResult<Option<ProofOfWorkPuzzle>>;
1408

1409
    /******** BLOCKCHAIN STATISTICS ********/
1410
    // Place all endpoints that relate to statistics of the blockchain here
1411

1412
    /// Return the block intervals of a range of blocks. Return value is the
1413
    /// number of milliseconds it took to mine the (canonical) block with the
1414
    /// specified height. Does not include the interval between genesis block
1415
    /// and block 1 since genesis block was not actually mined and its timestamp
1416
    /// doesn't carry the same meaning as those of later blocks.
1417
    async fn block_intervals(
1418
        token: rpc_auth::Token,
1419
        last_block: BlockSelector,
1420
        max_num_blocks: Option<usize>,
1421
    ) -> RpcResult<Option<Vec<(u64, u64)>>>;
1422

1423
    /// Return the difficulties of a range of blocks.
1424
    ///
1425
    /// ```no_run
1426
    /// # use anyhow::Result;
1427
    /// use neptune_cash::models::blockchain::block::block_selector::BlockSelector;
1428
    /// # use neptune_cash::rpc_server::RPCClient;
1429
    /// # use neptune_cash::rpc_auth;
1430
    /// # use tarpc::tokio_serde::formats::Json;
1431
    /// # use tarpc::serde_transport::tcp;
1432
    /// # use tarpc::client;
1433
    /// # use tarpc::context;
1434
    /// #
1435
    /// # #[tokio::main]
1436
    /// # async fn main() -> Result<()>{
1437
    /// #
1438
    /// # // create a serde/json transport over tcp.
1439
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1440
    /// #
1441
    /// # // create an rpc client using the transport.
1442
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1443
    /// #
1444
    /// # // Defines cookie hint
1445
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1446
    /// #
1447
    /// # // load the cookie file from disk and assign it to a token
1448
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1449
    /// #
1450
    /// // sets the last block
1451
    /// let last_block : BlockSelector = BlockSelector::Genesis;
1452
    ///
1453
    /// // set maximum number of blocks to 5 blocks
1454
    /// let max_num_blocks : Option<usize> = Some(5);
1455
    ///
1456
    /// // query neptune-core server to get difficulties of a range of blocks
1457
    /// let block_difficulties = client.block_difficulties(context::current(), token, last_block, max_num_blocks).await??;
1458
    /// # Ok(())
1459
    /// # }
1460
    /// ```
1461
    async fn block_difficulties(
1462
        token: rpc_auth::Token,
1463
        last_block: BlockSelector,
1464
        max_num_blocks: Option<usize>,
1465
    ) -> RpcResult<Vec<(u64, Difficulty)>>;
1466

1467
    /******** PEER INTERACTIONS ********/
1468

1469
    /// Broadcast transaction notifications for all transactions in this node's
1470
    /// mempool.
1471
    async fn broadcast_all_mempool_txs(token: rpc_auth::Token) -> RpcResult<()>;
1472

1473
    /******** CHANGE THINGS ********/
1474
    // Place all things that change state here
1475

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

1509
    /// Clears standing for ip, whether connected or not
1510
    ///
1511
    /// ```no_run
1512
    /// # use anyhow::Result;
1513
    /// # use neptune_cash::rpc_server::RPCClient;
1514
    /// # use neptune_cash::rpc_auth;
1515
    /// # use tarpc::tokio_serde::formats::Json;
1516
    /// # use tarpc::serde_transport::tcp;
1517
    /// # use tarpc::client;
1518
    /// # use tarpc::context;
1519
    /// # use std::net::{IpAddr, Ipv4Addr};
1520
    /// #
1521
    /// # #[tokio::main]
1522
    /// # async fn main() -> Result<()>{
1523
    /// #
1524
    /// # // create a serde/json transport over tcp.
1525
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1526
    /// #
1527
    /// // create an rpc client using the transport.
1528
    /// let client = RPCClient::new(client::Config::default(), transport).spawn();
1529
    ///
1530
    /// // Defines cookie hint
1531
    /// let cookie_hint = client.cookie_hint(context::current()).await??;
1532
    ///
1533
    /// // load the cookie file from disk and assign it to a token
1534
    /// let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1535
    ///
1536
    /// // IP address 87.23.90.12 to clear standing
1537
    /// let ip = IpAddr::V4(Ipv4Addr::new(87, 23, 90, 12));
1538
    ///
1539
    /// // neptune-core server clears standing for all peers that are connected or not
1540
    /// let _ = client.clear_standing_by_ip(context::current(), token, ip).await??;
1541
    /// # Ok(())
1542
    /// # }
1543
    /// ```
1544
    async fn clear_standing_by_ip(token: rpc_auth::Token, ip: IpAddr) -> RpcResult<()>;
1545

1546
    /// todo: docs.
1547
    ///
1548
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::spendable_inputs()]
1549
    async fn spendable_inputs(token: rpc_auth::Token) -> RpcResult<TxInputList>;
1550

1551
    /// retrieve spendable inputs sufficient to cover spend_amount by applying selection policy.
1552
    ///
1553
    /// see [InputSelectionPolicy]
1554
    ///
1555
    /// pub enum InputSelectionPolicy {
1556
    ///     Random,
1557
    ///     ByNativeCoinAmount(SortOrder),
1558
    ///     ByUtxoSize(SortOrder),
1559
    /// }
1560
    ///
1561
    /// todo: docs.
1562
    ///
1563
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::select_spendable_inputs()]
1564
    async fn select_spendable_inputs(
1565
        token: rpc_auth::Token,
1566
        policy: InputSelectionPolicy,
1567
        spend_amount: NativeCurrencyAmount,
1568
    ) -> RpcResult<TxInputList>;
1569

1570
    /// generate tx outputs from list of OutputFormat.
1571
    ///
1572
    /// OutputFormat can be address:amount, address:amount:medium, address:utxo,
1573
    /// address:utxo:medium, tx_output, etc.
1574
    ///
1575
    /// todo: docs.
1576
    ///
1577
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::generate_tx_outputs()]
1578
    async fn generate_tx_outputs(
1579
        token: rpc_auth::Token,
1580
        outputs: Vec<OutputFormat>,
1581
    ) -> RpcResult<TxOutputList>;
1582

1583
    /// todo: docs.
1584
    ///
1585
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::generate_tx_details()]
1586
    async fn generate_tx_details(
1587
        token: rpc_auth::Token,
1588
        tx_inputs: TxInputList,
1589
        tx_outputs: TxOutputList,
1590
        change_policy: ChangePolicy,
1591
        fee: NativeCurrencyAmount,
1592
    ) -> RpcResult<TransactionDetails>;
1593

1594
    /// todo: docs.
1595
    ///
1596
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::generate_witness_proof()]
1597
    async fn generate_witness_proof(
1598
        token: rpc_auth::Token,
1599
        tx_details: TransactionDetails,
1600
    ) -> RpcResult<TransactionProof>;
1601

1602
    /// assemble a transaction from TransactionDetails and a TransactionProof.
1603
    ///
1604
    /// todo: docs.
1605
    ///
1606
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::assemble_transaction()]
1607
    async fn assemble_transaction(
1608
        token: rpc_auth::Token,
1609
        transaction_details: TransactionDetails,
1610
        transaction_proof: TransactionProof,
1611
    ) -> RpcResult<Transaction>;
1612

1613
    /// assemble transaction artifacts from TransactionDetails and a TransactionProof.
1614
    ///
1615
    /// todo: docs.
1616
    ///
1617
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::assemble_transaction_artifacts()]
1618
    async fn assemble_transaction_artifacts(
1619
        token: rpc_auth::Token,
1620
        transaction_details: TransactionDetails,
1621
        transaction_proof: TransactionProof,
1622
    ) -> RpcResult<TxCreationArtifacts>;
1623

1624
    /// record transaction and initiate broadcast to peers
1625
    ///
1626
    /// todo: docs.
1627
    ///
1628
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::record_and_broadcast_transaction()]
1629
    async fn record_and_broadcast_transaction(
1630
        token: rpc_auth::Token,
1631
        tx_artifacts: TxCreationArtifacts,
1632
    ) -> RpcResult<()>;
1633

1634
    /// Send coins to one or more recipients
1635
    ///
1636
    /// note: sending is rate-limited to 2 sends per block until block
1637
    /// 25000 is reached.
1638
    ///
1639
    /// `outputs` is a list of transaction outputs in any format supported by [OutputFormat].
1640
    ///
1641
    /// `change_policy` specifies how to handle change in the typical case that
1642
    /// the transaction input amount exceeds the output amount.
1643
    ///
1644
    /// `fee` represents the fee in native coins to pay the miner who mines
1645
    /// the block that initially confirms the resulting transaction.
1646
    ///
1647
    /// a [Digest] of the resulting [Transaction](crate::models::blockchain::transaction::Transaction) is returned on success, else [None].
1648
    ///
1649
    /// A list of the encoded transaction notifications is also returned. The
1650
    /// relevant notifications should be sent to the transaction receiver(s) in
1651
    /// case `Offchain` notification is used for any output(s).
1652
    ///
1653
    /// ```no_run
1654
    /// # use anyhow::Result;
1655
    /// # use neptune_cash::config_models::network::Network;
1656
    /// # use neptune_cash::models::blockchain::type_scripts::native_currency_amount::NativeCurrencyAmount;
1657
    /// # use neptune_cash::models::state::wallet::address::ReceivingAddress;
1658
    /// # use neptune_cash::models::state::wallet::utxo_notification::UtxoNotificationMedium;
1659
    /// # use neptune_cash::rpc_server::RPCClient;
1660
    /// # use neptune_cash::rpc_auth;
1661
    /// # use neptune_cash::api::export::ChangePolicy;
1662
    /// # use neptune_cash::api::export::OutputFormat;
1663
    /// # use tarpc::tokio_serde::formats::Json;
1664
    /// # use tarpc::serde_transport::tcp;
1665
    /// # use tarpc::client;
1666
    /// # use tarpc::context;
1667
    /// # use std::net::IpAddr;
1668
    /// #
1669
    /// # #[tokio::main]
1670
    /// # async fn main() -> Result<()>{
1671
    /// #
1672
    /// # // create a serde/json transport over tcp.
1673
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1674
    /// #
1675
    /// # // create an rpc client using the transport.
1676
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1677
    /// #
1678
    /// # // Defines cookie hint
1679
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1680
    /// #
1681
    /// # // load the cookie file from disk and assign it to a token
1682
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1683
    /// #
1684
    /// // List of receiving addresses and the amounts to send
1685
    /// let outputs: Vec<OutputFormat> = vec![
1686
    ///     (ReceivingAddress::from_bech32m("nolgam1lf8vc5xpa4jf9vjakts632fct5q80d4m6tax39nrl8c55dta2h7n7lnkh9pmwckl0ndwc7897xwfgx5vv02xdt3099z62222wazz7tjl6umzewla9xzxyqefh2w47v4eh0xzvfsxjk6kq5u84rwwlflq7cs726ljttl6ls860te04cwpy5kk8n40qqjnps0gdp46namhsa3cqt0uc0s5e34h6s5rw2kl77uvvs4rlnn5t8wtuefsduuccwsxmk27r8d48g49swgafhj6wmvu5cx3lweqhnxgdgm7mmdq7ck6wkurw2jzl64k9u34kzgu9stgd47ljzte0hz0n2lcng83vtpf0u9f4hggw4llqsz2fqpe4096d9v5fzg7xvxg6zvr7gksq4yqgn8shepg5xsczmzz256m9c6r8zqdkzy4tk9he59ndtdkrrr8u5v6ztnvkvmy4sed7p7plm2y09sgksw6zcjayls4wl9fnqu97kyx9cdknksar7h8jetygur979rt5arcwmvp2dy3ynt6arna2yjpevt9209v9g2p5cvp6gjp9850w3w6afeg8yuhp6u447hrudcssyjauqa2p7jk4tz37wg70yrdhsgn35sc0hdkclvpapu75dgtmswk0vtgadx44mqdps6ry6005xqups9dpc93u66qj9j7lfaqgdqrrfg9pkxhjl99ge387rh257x2phfvjvc8y66p22wax8myyhm7mgmlxu9gug0km3lmn4lzcyj32mduy6msy4kfn5z2tr67zfxadnj6wc0av27mk0j90pf67uzp9ps8aekr24kpv5n3qeczfznen9vj67ft95s93t26l8uh87qr6kp8lsyuzm4h36de830h6rr3lhg5ac995nrsu6h0p56t5tnglvx0s02mr0ts95fgcevveky5kkw6zgj6jd5m3n5ljhw862km8sedr30xvg8t9vh409ufuxdnfuypvqdq49z6mp46p936pjzwwqjda6yy5wuxx9lffrxwcmfqzch6nz2l4mwd2vlsdr58vhygppy6nm6tduyemw4clwj9uac4v990xt6jt7e2al7m6sjlq4qgxfjf4ytx8f5j460vvr7yac9hsvlsat2vh5gl55mt4wr7v5p3m6k5ya5442xdarastxlmpf2vqz5lusp8tlglxkj0jksgwqgtj6j0kxwmw40egpzs5rr996xpv8wwqyja4tmw599n9fh77f5ruxk69vtpwl9z5ezmdn92cpyyhwff59ypp0z5rv98vdvm67umqzt0ljjan30u3a8nga35fdy450ht9gef24mveucxqwv5aflge5r3amxsvd7l30j9kcqm7alq0ks2wqpde7pdct2gmvafxvjg3ad0a3h58assjaszvmykl3k5tn238gstm2shlvad4a53mm5ztvp5q2zt4pdzj0ssevlkumwhc0g5cxnxc9u7rh9gffkq7h9ufcxkgtghe32sv3vwzkessr52mcmajt83lvz45wqru9hht8cytfedtjlv7z7en6pp0guja85ft3rv6hzf2e02e7wfu38s0nyfzkc2qy2k298qtmxgrpduntejtvenr80csnckajnhu44399tkm0a7wdldalf678n9prd54twwlw24xhppxqlquatfztllkeejlkfxuayddwagh6uzx040tqlcs7hcflnu0ywynmz0chz48qcx7dsc4gpseu0dqvmmezpuv0tawm78nleju2vp4lkehua56hrnuj2wuc5lqvxlnskvp53vu7e2399pgp7xcwe3ww23qcd9pywladq34nk6cwcvtj3vdfgwf6r7s6vq46y2x05e043nj6tu8am2und8z3ftf3he5ccjxamtnmxfd79m04ph36kzx6e789dhqrwmwcfrn9ulsedeplk3dvrmad6f20y9qfl6n6kzaxkmmmaq4d6s5rl4kmhc7fcdkrkandw2jxdjckuscu56syly8rtjatj4j2ug23cwvep3dgcdvmtr32296nf9vdl3rcu0r7hge23ydt83k5nhtnexuqrnamveacz6c43eay9nz4pjjwjatkgp80lg9tnf5kdr2eel8s2fk6v338x4hu00htemm5pq6qlucqqq5tchhtekjzdu50erqd2fkdu9th3wl0mqxz5u7wnpgwgpammv2yqpa5znljegyhke0dz9vg27uh5t5x6qdgf7vu54lqssejekwzfxchjyq2s8frm9fmt688w76aug56v6n3w5xdre78xplfsdw3e4j6dc5w7tf83r25re0duq6h8z54wnkqr9yh2k0skjqea4elgcr4aw7hks9m8w3tx8w9xlxpqqll2zeql55ew7e90dyuynkqxfuqzv45t22ljamdll3udvqrllprdltthzm866jdaxkkrnryj4cmc2m7sk99clgql3ynrhe9kynqn4mh3tepk8dtq7cndtc2hma29s4cuylsvg04s70uyr53w5656su5rjem5egss08zrfaef0mww6t8pr26uph2n8a2cs55ydx4xhasjqk7xs0akh6f26j2ec4d8pd0kdf4jya6p9jl48wmy5autdpw2q8mehrq6kypt573genj66l5zkq6xvrdqugmfczxa2gj9ylx3pgpjqnhuem9udfkj9qr2y8lh728sr7uaedu5wwmfa72ykh395jqh7f7f9p2gskn6u7k844kpnwe3eqv84pl53r6x9af88a8ey7298njdg03h8mxqz2x6z8ys3qpuxq768tjq0zhrnjgns8d78euzwsvx6vn4f9tftrp68zcch3h75mc9drpt7tpvnyyqfjuqclxhdwhdwtsakecv04p9r3jx90htql9a3ht5mxrj4ercv4cd52wk4qhu7dn4tqe7yclqx2l36gcsrzmdlv440qls7qjpq6k95mst485vpennnur8h62a7d7syvyer89qtyfzlfhz8a5a0x5tuwhc9mah0e944xzhsc6uvpv8vat44w7r3xyw8q85y77jux8zhndrhdn36swryffqmpkxgcw4g29q40sul4fl5vrfru08a5j3rd3jl8799srpf2xqpxq38wwvhr4mxqf5wwdqfqq7harshggvufzlgn0l9fq0j76dyuge75jmzy8celvw6wesfs82n4jw2k8jnus2zds5a67my339uuzka4w72tau6j7wyu0lla0mcjpaflphsuy7f2phev6tr8vc9nj2mczkeg4vy3n5jkgecwgrvwu3vw9x5knpkxzv8kw3dpzzxy3rvrs56vxw8ugmyz2vdj6dakjyq3feym4290l7hgdt0ac5u49sekezzf0ghwmlek4h75fkzpvuly9zupw32dd3l9my282nekgk78fe6ayjyhczetxf8r82yd2askl52kmupr9xaxw0jd08dsd3523ea6ge48384rlmt4mu4w4x0q9s", Network::Main)?, NativeCurrencyAmount::coins(20)).into(),
1687
    ///     (ReceivingAddress::from_bech32m("nolgam1ld9vc5xpa4jf9vjakts632fct5q80d4m6tax39nrl8c55dta2h7n7lnkh9pmwckl0ndwc7897xwfgx5vv02xdt3099z62222wazz7tjl6umzewla9xzxyqefh2w47v4eh0xzvfsxjk6kq5u84rwwlflq7cs726ljttl6ls860te04cwpy5kk8n40qqjnps0gdp46namhsa3cqt0uc0s5e34h6s5rw2kl77uvvs4rlnn5t8wtuefsduuccwsxmk27r8d48g49swgafhj6wmvu5cx3lweqhnxgdgm7mmdq7ck6wkurw2jzl64k9u34kzgu9stgd47ljzte0hz0n2lcng83vtpf0u9f4hggw4llqsz2fqpe4096d9v5fzg7xvxg6zvr7gksq4yqgn8shepg5xsczmzz256m9c6r8zqdkzy4tk9he59ndtdkrrr8u5v6ztnvkvmy4sed7p7plm2y09sgksw6zcjayls4wl9fnqu97kyx9cdknksar7h8jetygur979rt5arcwmvp2dy3ynt6arna2yjpevt9209v9g2p5cvp6gjp9850w3w6afeg8yuhp6u447hrudcssyjauqa2p7jk4tz37wg70yrdhsgn35sc0hdkclvpapu75dgtmswk0vtgadx44mqdps6ry6005xqups9dpc93u66qj9j7lfaqgdqrrfg9pkxhjl99ge387rh257x2phfvjvc8y66p22wax8myyhm7mgmlxu9gug0km3lmn4lzcyj32mduy6msy4kfn5z2tr67zfxadnj6wc0av27mk0j90pf67uzp9ps8aekr24kpv5n3qeczfznen9vj67ft95s93t26l8uh87qr6kp8lsyuzm4h36de830h6rr3lhg5ac995nrsu6h0p56t5tnglvx0s02mr0ts95fgcevveky5kkw6zgj6jd5m3n5ljhw862km8sedr30xvg8t9vh409ufuxdnfuypvqdq49z6mp46p936pjzwwqjda6yy5wuxx9lffrxwcmfqzch6nz2l4mwd2vlsdr58vhygppy6nm6tduyemw4clwj9uac4v990xt6jt7e2al7m6sjlq4qgxfjf4ytx8f5j460vvr7yac9hsvlsat2vh5gl55mt4wr7v5p3m6k5ya5442xdarastxlmpf2vqz5lusp8tlglxkj0jksgwqgtj6j0kxwmw40egpzs5rr996xpv8wwqyja4tmw599n9fh77f5ruxk69vtpwl9z5ezmdn92cpyyhwff59ypp0z5rv98vdvm67umqzt0ljjan30u3a8nga35fdy450ht9gef24mveucxqwv5aflge5r3amxsvd7l30j9kcqm7alq0ks2wqpde7pdct2gmvafxvjg3ad0a3h58assjaszvmykl3k5tn238gstm2shlvad4a53mm5ztvp5q2zt4pdzj0ssevlkumwhc0g5cxnxc9u7rh9gffkq7h9ufcxkgtghe32sv3vwzkessr52mcmajt83lvz45wqru9hht8cytfedtjlv7z7en6pp0guja85ft3rv6hzf2e02e7wfu38s0nyfzkc2qy2k298qtmxgrpduntejtvenr80csnckajnhu44399tkm0a7wdldalf678n9prd54twwlw24xhppxqlquatfztllkeejlkfxuayddwagh6uzx040tqlcs7hcflnu0ywynmz0chz48qcx7dsc4gpseu0dqvmmezpuv0tawm78nleju2vp4lkehua56hrnuj2wuc5lqvxlnskvp53vu7e2399pgp7xcwe3ww23qcd9pywladq34nk6cwcvtj3vdfgwf6r7s6vq46y2x05e043nj6tu8am2und8z3ftf3he5ccjxamtnmxfd79m04ph36kzx6e789dhqrwmwcfrn9ulsedeplk3dvrmad6f20y9qfl6n6kzaxkmmmaq4d6s5rl4kmhc7fcdkrkandw2jxdjckuscu56syly8rtjatj4j2ug23cwvep3dgcdvmtr32296nf9vdl3rcu0r7hge23ydt83k5nhtnexuqrnamveacz6c43eay9nz4pjjwjatkgp80lg9tnf5kdr2eel8s2fk6v338x4hu00htemm5pq6qlucqqq5tchhtekjzdu50erqd2fkdu9th3wl0mqxz5u7wnpgwgpammv2yqpa5znljegyhke0dz9vg27uh5t5x6qdgf7vu54lqssejekwzfxchjyq2s8frm9fmt688w76aug56v6n3w5xdre78xplfsdw3e4j6dc5w7tf83r25re0duq6h8z54wnkqr9yh2k0skjqea4elgcr4aw7hks9m8w3tx8w9xlxpqqll2zeql55ew7e90dyuynkqxfuqzv45t22ljamdll3udvqrllprdltthzm866jdaxkkrnryj4cmc2m7sk99clgql3ynrhe9kynqn4mh3tepk8dtq7cndtc2hma29s4cuylsvg04s70uyr53w5656su5rjem5egss08zrfaef0mww6t8pr26uph2n8a2cs55ydx4xhasjqk7xs0akh6f26j2ec4d8pd0kdf4jya6p9jl48wmy5autdpw2q8mehrq6kypt573genj66l5zkq6xvrdqugmfczxa2gj9ylx3pgpjqnhuem9udfkj9qr2y8lh728sr7uaedu5wwmfa72ykh395jqh7f7f9p2gskn6u7k844kpnwe3eqv84pl53r6x9af88a8ey7298njdg03h8mxqz2x6z8ys3qpuxq768tjq0zhrnjgns8d78euzwsvx6vn4f9tftrp68zcch3h75mc9drpt7tpvnyyqfjuqclxhdwhdwtsakecv04p9r3jx90htql9a3ht5mxrj4ercv4cd52wk4qhu7dn4tqe7yclqx2l36gcsrzmdlv440qls7qjpq6k95mst485vpennnur8h62a7d7syvyer89qtyfzlfhz8a5a0x5tuwhc9mah0e944xzhsc6uvpv8vat44w7r3xyw8q85y77jux8zhndrhdn36swryffqmpkxgcw4g29q40sul4fl5vrfru08a5j3rd3jl8799srpf2xqpxq38wwvhr4mxqf5wwdqfqq7harshggvufzlgn0l9fq0j76dyuge75jmzy8celvw6wesfs82n4jw2k8jnus2zds5a67my339uuzka4w72tau6j7wyu0lla0mcjpaflphsuy7f2phev6tr8vc9nj2mczkeg4vy3n5jkgecwgrvwu3vw9x5knpkxzv8kw3dpzzxy3rvrs56vxw8ugmyz2vdj6dakjyq3feym4290l7hgdt0ac5u49sekezzf0ghwmlek4h75fkzpvuly9zupw32dd3l9my282nekgk78fe6ayjyhczetxf8r82yd2askl52kmupr9xaxw0jd08dsd3523ea6ge48384rlmt4mu4w4x0q9s", Network::Main)?, NativeCurrencyAmount::coins(57)).into(),
1688
    /// ];
1689
    ///
1690
    /// // change policy.
1691
    /// // default is recover to next unused key, via onchain notification
1692
    /// let change_policy = ChangePolicy::default();
1693
    /// #
1694
    /// // Max fee
1695
    /// let fee : NativeCurrencyAmount = NativeCurrencyAmount::coins(10);
1696
    /// #
1697
    /// // neptune-core server sends token to a single recipient
1698
    /// let send_result = client.send(context::current(), token, outputs, change_policy, fee).await??;
1699
    /// # Ok(())
1700
    /// # }
1701
    /// ```
1702
    async fn send(
1703
        token: rpc_auth::Token,
1704
        outputs: Vec<OutputFormat>,
1705
        change_policy: ChangePolicy,
1706
        fee: NativeCurrencyAmount,
1707
    ) -> RpcResult<TxCreationArtifacts>;
1708

1709
    /// upgrades a transaction's proof.
1710
    ///
1711
    /// ignored if the transaction is already upgraded to level of supplied
1712
    /// proof (or higher)
1713
    ///
1714
    /// experimental and untested!  do not use yet!!!
1715
    ///
1716
    /// todo: docs.
1717
    ///
1718
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::upgrade_tx_proof()]
1719
    async fn upgrade_tx_proof(
1720
        token: rpc_auth::Token,
1721
        transaction_id: TransactionKernelId,
1722
        transaction_proof: TransactionProof,
1723
    ) -> RpcResult<()>;
1724

1725
    /// todo: docs.
1726
    ///
1727
    /// meanwhile see [tx_initiation::initiator::TransactionInitiator::proof_type()]
1728
    async fn proof_type(
1729
        token: rpc_auth::Token,
1730
        txid: TransactionKernelId,
1731
    ) -> RpcResult<TransactionProofType>;
1732

1733
    /// claim a utxo
1734
    ///
1735
    /// The input string must be a valid bech32m encoded `UtxoTransferEncrypted`
1736
    /// for the current network and the wallet must have the corresponding
1737
    /// `SpendingKey` for decryption.
1738
    ///
1739
    /// upon success, a new `ExpectedUtxo` will be added to the local wallet
1740
    /// state.
1741
    ///
1742
    /// if the utxo has already been claimed, this call has no effect.
1743
    ///
1744
    /// Return true if a new expected UTXO was added, otherwise false.
1745
    ///
1746
    /// ```no_run
1747
    /// # use anyhow::Result;
1748
    /// # use neptune_cash::rpc_server::RPCClient;
1749
    /// # use neptune_cash::rpc_auth;
1750
    /// # use tarpc::tokio_serde::formats::Json;
1751
    /// # use tarpc::serde_transport::tcp;
1752
    /// # use tarpc::client;
1753
    /// # use tarpc::context;
1754
    /// #
1755
    /// # #[tokio::main]
1756
    /// # async fn main() -> Result<()>{
1757
    /// #
1758
    /// # // create a serde/json transport over tcp.
1759
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1760
    /// #
1761
    /// # // create an rpc client using the transport.
1762
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1763
    /// #
1764
    /// # // Defines cookie hint
1765
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1766
    /// #
1767
    /// # // load the cookie file from disk and assign it to a token
1768
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1769
    /// #
1770
    /// // Encryted value of utxo transfer
1771
    /// let utxo_transfer_encrypted = "XXXXXXX".to_string();
1772
    ///
1773
    /// // max search depth is set to 3
1774
    /// let max_search_depth : Option<u64> = Some(3);
1775
    ///
1776
    /// // claim utxo
1777
    /// let utxo_claimed = client.claim_utxo(context::current(), token, utxo_transfer_encrypted, max_search_depth).await??;
1778
    /// # Ok(())
1779
    /// # }
1780
    /// ```
1781
    async fn claim_utxo(
1782
        token: rpc_auth::Token,
1783
        utxo_transfer_encrypted: String,
1784
        max_search_depth: Option<u64>,
1785
    ) -> RpcResult<bool>;
1786

1787
    /// Delete all transactions from the mempool.
1788
    async fn clear_mempool(token: rpc_auth::Token) -> RpcResult<()>;
1789

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

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

1856
    /// mine a series of blocks to the node's wallet.
1857
    ///
1858
    /// Can be used only if the network uses mock blocks.
1859
    /// (presently only the regtest network)
1860
    ///
1861
    /// these blocks can be generated quickly because they do not have
1862
    /// a real ZK proof.  they have a witness "proof" and will validate correctly.
1863
    /// witness proofs contain secrets that must not be shared, so this is
1864
    /// allowed only on the regtest network, for development purposes.
1865
    ///
1866
    /// The timestamp of each block will be the current system time, meaning
1867
    /// that they will be temporally very close to eachother.
1868
    ///
1869
    /// see [api::regtest::RegTest::mine_blocks_to_wallet()]
1870
    async fn mine_blocks_to_wallet(token: rpc_auth::Token, n_blocks: u32) -> RpcResult<()>;
1871

1872
    /// Provide a PoW-solution to the current block proposal.
1873
    ///
1874
    /// If the solution is considered valid by the running node, the new block
1875
    /// is broadcast to all peers on the network, and `true` is returned.
1876
    /// Otherwise the provided solution is ignored, and `false` is returned.
1877
    async fn provide_pow_solution(
1878
        token: rpc_auth::Token,
1879
        nonce: Digest,
1880
        proposal_id: Digest,
1881
    ) -> RpcResult<bool>;
1882

1883
    /// mark MUTXOs as abandoned
1884
    ///
1885
    /// ```no_run
1886
    /// # use anyhow::Result;
1887
    /// # use neptune_cash::rpc_server::RPCClient;
1888
    /// # use neptune_cash::rpc_auth;
1889
    /// # use tarpc::tokio_serde::formats::Json;
1890
    /// # use tarpc::serde_transport::tcp;
1891
    /// # use tarpc::client;
1892
    /// # use tarpc::context;
1893
    /// #
1894
    /// # #[tokio::main]
1895
    /// # async fn main() -> Result<()>{
1896
    /// #
1897
    /// # // create a serde/json transport over tcp.
1898
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1899
    /// #
1900
    /// # // create an rpc client using the transport.
1901
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1902
    /// #
1903
    /// # // Defines cookie hint
1904
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1905
    /// #
1906
    /// # // load the cookie file from disk and assign it to a token
1907
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1908
    /// #
1909
    ///  // marks mutxos as abandoned
1910
    /// let abandoned_monitored_utxos = client.prune_abandoned_monitored_utxos(context::current(), token).await??;
1911
    /// # Ok(())
1912
    /// # }
1913
    /// ```
1914
    async fn prune_abandoned_monitored_utxos(token: rpc_auth::Token) -> RpcResult<usize>;
1915

1916
    /// Gracious shutdown.
1917
    ///
1918
    /// ```no_run
1919
    /// # use anyhow::Result;
1920
    /// # use neptune_cash::rpc_server::RPCClient;
1921
    /// # use neptune_cash::rpc_auth;
1922
    /// # use tarpc::tokio_serde::formats::Json;
1923
    /// # use tarpc::serde_transport::tcp;
1924
    /// # use tarpc::client;
1925
    /// # use tarpc::context;
1926
    /// #
1927
    /// # #[tokio::main]
1928
    /// # async fn main() -> Result<()>{
1929
    /// #
1930
    /// # // create a serde/json transport over tcp.
1931
    /// # let transport = tcp::connect("127.0.0.1:9799", Json::default).await?;
1932
    /// #
1933
    /// # // create an rpc client using the transport.
1934
    /// # let client = RPCClient::new(client::Config::default(), transport).spawn();
1935
    /// #
1936
    /// # // Defines cookie hint
1937
    /// # let cookie_hint = client.cookie_hint(context::current()).await??;
1938
    /// #
1939
    /// # // load the cookie file from disk and assign it to a token
1940
    /// # let token : rpc_auth::Token = rpc_auth::Cookie::try_load(&cookie_hint.data_directory).await?.into();
1941
    /// #
1942
    ///  // shutdowns the node
1943
    /// let is_shutdown = client.shutdown(context::current(), token).await??;
1944
    /// # Ok(())
1945
    /// # }
1946
    async fn shutdown(token: rpc_auth::Token) -> RpcResult<bool>;
1947
}
1948

1949
#[derive(Clone)]
1950
pub(crate) struct NeptuneRPCServer {
1951
    pub(crate) state: GlobalStateLock,
1952
    pub(crate) rpc_server_to_main_tx: tokio::sync::mpsc::Sender<RPCServerToMain>,
1953

1954
    // copy of DataDirectory for this neptune-core instance.
1955
    data_directory: DataDirectory,
1956

1957
    // list of tokens that are valid.  RPC clients must present a token that
1958
    // matches one of these.  there should only be one of each `Token` variant
1959
    // in the list (dups ignored).
1960
    valid_tokens: Vec<rpc_auth::Token>,
1961
}
1962

1963
impl NeptuneRPCServer {
1964
    /// instantiate a new [NeptuneRPCServer]
1965
    pub fn new(
29✔
1966
        state: GlobalStateLock,
29✔
1967
        rpc_server_to_main_tx: tokio::sync::mpsc::Sender<RPCServerToMain>,
29✔
1968
        data_directory: DataDirectory,
29✔
1969
        valid_tokens: Vec<rpc_auth::Token>,
29✔
1970
    ) -> Self {
29✔
1971
        Self {
29✔
1972
            state,
29✔
1973
            valid_tokens,
29✔
1974
            rpc_server_to_main_tx,
29✔
1975
            data_directory,
29✔
1976
        }
29✔
1977
    }
29✔
1978

1979
    async fn confirmations_internal(&self, state: &GlobalState) -> Option<BlockHeight> {
1✔
1980
        match state.get_latest_balance_height().await {
1✔
1981
            Some(latest_balance_height) => {
×
1982
                let tip_block_header = state.chain.light_state().header();
×
1983

1984
                assert!(tip_block_header.height >= latest_balance_height);
×
1985

1986
                // subtract latest balance height from chain tip.
1987
                //
1988
                // we add 1 to the result because the block that a tx is confirmed
1989
                // in is considered the 1st confirmation.
1990
                //
1991
                // note: BlockHeight is u64 internally and BlockHeight::sub() returns i128.
1992
                //       The subtraction and cast is safe given we passed the above assert.
1993
                let confirmations: BlockHeight =
×
1994
                    ((tip_block_header.height - latest_balance_height) as u64 + 1).into();
×
1995
                Some(confirmations)
×
1996
            }
1997
            None => None,
1✔
1998
        }
1999
    }
1✔
2000

2001
    /// Return temperature of CPU, if available.
2002
    fn cpu_temp_inner() -> Option<f32> {
1✔
2003
        let current_system = System::new();
1✔
2004
        current_system.cpu_temp().ok()
1✔
2005
    }
1✔
2006

2007
    /// Assemble a data for the wallet to register the UTXO. Returns `Ok(None)`
2008
    /// if the UTXO has already been claimed by the wallet.
2009
    ///
2010
    /// `max_search_depth` denotes how many blocks back from tip we attempt
2011
    /// to find the transaction in a block. `None` means unlimited.
2012
    ///
2013
    /// `encrypted_utxo_notification` is expected to hold encrypted data about
2014
    /// a future or past UTXO, which can be claimed by this client.
2015
    async fn claim_utxo_inner(
17✔
2016
        &self,
17✔
2017
        encrypted_utxo_notification: String,
17✔
2018
        max_search_depth: Option<u64>,
17✔
2019
    ) -> Result<Option<ClaimUtxoData>, error::ClaimError> {
17✔
2020
        let span = tracing::debug_span!("Claim UTXO inner");
17✔
2021
        let _enter = span.enter();
17✔
2022

2023
        // deserialize UtxoTransferEncrypted from bech32m string.
2024
        let utxo_transfer_encrypted = EncryptedUtxoNotification::from_bech32m(
17✔
2025
            &encrypted_utxo_notification,
17✔
2026
            self.state.cli().network,
17✔
2027
        )?;
×
2028

2029
        // // acquire global state read lock
2030
        let state = self.state.lock_guard().await;
17✔
2031

2032
        // find known spending key by receiver_identifier
2033
        let spending_key = state
17✔
2034
            .wallet_state
17✔
2035
            .find_known_spending_key_for_receiver_identifier(
17✔
2036
                utxo_transfer_encrypted.receiver_identifier,
17✔
2037
            )
2038
            .ok_or(error::ClaimError::UtxoUnknown)?;
17✔
2039

2040
        // decrypt utxo_transfer_encrypted into UtxoTransfer
2041
        let utxo_notification = utxo_transfer_encrypted.decrypt_with_spending_key(&spending_key)?;
17✔
2042

2043
        tracing::debug!("claim-utxo: decrypted {:#?}", utxo_notification);
17✔
2044

2045
        // search for matching monitored utxo and return early if found.
2046
        if state
17✔
2047
            .wallet_state
17✔
2048
            .find_monitored_utxo(&utxo_notification.utxo, utxo_notification.sender_randomness)
17✔
2049
            .await
17✔
2050
            .is_some()
17✔
2051
        {
2052
            info!("found monitored utxo. Returning early.");
8✔
2053
            return Ok(None);
8✔
2054
        }
9✔
2055

2056
        // construct an IncomingUtxo
2057
        let incoming_utxo = IncomingUtxo::from_utxo_notification_payload(
9✔
2058
            utxo_notification,
9✔
2059
            spending_key.privacy_preimage(),
9✔
2060
        );
2061

2062
        // Check if we can satisfy typescripts
2063
        if !incoming_utxo.utxo.all_type_script_states_are_valid() {
9✔
2064
            let err = error::ClaimError::InvalidTypeScript;
×
2065
            warn!("{}", err.to_string());
×
2066
            return Err(err);
×
2067
        }
9✔
2068

2069
        // check if wallet is already expecting this utxo.
2070
        let addition_record = incoming_utxo.addition_record();
9✔
2071
        let has_expected_utxo = state.wallet_state.has_expected_utxo(addition_record).await;
9✔
2072

2073
        // Check if UTXO has already been mined in a transaction.
2074
        let mined_in_block = state
9✔
2075
            .chain
9✔
2076
            .archival_state()
9✔
2077
            .find_canonical_block_with_output(addition_record, max_search_depth)
9✔
2078
            .await;
9✔
2079
        let maybe_prepared_mutxo = match mined_in_block {
9✔
2080
            Some(block) => {
2✔
2081
                let aocl_leaf_index = {
2✔
2082
                    // Find matching AOCL leaf index that must be in this block
2083
                    let last_aocl_index_in_block =
2✔
2084
                        block.mutator_set_accumulator_after().aocl.num_leafs() - 1;
2✔
2085
                    let num_outputs_in_block: u64 = block
2✔
2086
                        .mutator_set_update()
2✔
2087
                        .additions
2✔
2088
                        .len()
2✔
2089
                        .try_into()
2✔
2090
                        .unwrap();
2✔
2091
                    let min_aocl_leaf_index = last_aocl_index_in_block - num_outputs_in_block + 1;
2✔
2092
                    let mut haystack = last_aocl_index_in_block;
2✔
2093
                    let ams = state.chain.archival_state().archival_mutator_set.ams();
2✔
2094
                    while ams.aocl.get_leaf_async(haystack).await
9✔
2095
                        != addition_record.canonical_commitment
9✔
2096
                    {
2097
                        assert!(haystack > min_aocl_leaf_index);
7✔
2098
                        haystack -= 1;
7✔
2099
                    }
2100

2101
                    haystack
2✔
2102
                };
2103
                let item = Tip5::hash(&incoming_utxo.utxo);
2✔
2104
                let ams = state.chain.archival_state().archival_mutator_set.ams();
2✔
2105
                let msmp = ams
2✔
2106
                    .restore_membership_proof(
2✔
2107
                        item,
2✔
2108
                        incoming_utxo.sender_randomness,
2✔
2109
                        incoming_utxo.receiver_preimage,
2✔
2110
                        aocl_leaf_index,
2✔
2111
                    )
2✔
2112
                    .await
2✔
2113
                    .map_err(|x| anyhow!("Could not restore mutator set membership proof. Is archival mutator set corrupted? Got error: {x}"))?;
2✔
2114

2115
                let tip_digest = state.chain.light_state().hash();
2✔
2116

2117
                let mut monitored_utxo = MonitoredUtxo::new(
2✔
2118
                    incoming_utxo.utxo.clone(),
2✔
2119
                    self.state.cli().number_of_mps_per_utxo,
2✔
2120
                );
2121
                monitored_utxo.confirmed_in_block = Some((
2✔
2122
                    block.hash(),
2✔
2123
                    block.header().timestamp,
2✔
2124
                    block.header().height,
2✔
2125
                ));
2✔
2126
                monitored_utxo.add_membership_proof_for_tip(tip_digest, msmp.clone());
2✔
2127

2128
                // Was UTXO already spent? If so, register it as such.
2129
                let msa = ams.accumulator().await;
2✔
2130
                if !msa.verify(item, &msmp) {
2✔
2131
                    warn!("Claimed UTXO was already spent. Marking it as such.");
×
2132

2133
                    if let Some(spending_block) = state
×
2134
                        .chain
×
2135
                        .archival_state()
×
2136
                        .find_canonical_block_with_input(
×
2137
                            msmp.compute_indices(item),
×
2138
                            max_search_depth,
×
2139
                        )
2140
                        .await
×
2141
                    {
2142
                        warn!(
×
2143
                            "Claimed UTXO was spent in block {}; which has height {}",
×
2144
                            spending_block.hash(),
×
2145
                            spending_block.header().height
×
2146
                        );
2147
                        monitored_utxo.mark_as_spent(&spending_block);
×
2148
                    } else {
2149
                        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.");
×
2150
                    }
2151
                }
2✔
2152

2153
                Some(monitored_utxo)
2✔
2154
            }
2155
            None => None,
7✔
2156
        };
2157

2158
        let expected_utxo = incoming_utxo.into_expected_utxo(UtxoNotifier::Cli);
9✔
2159
        Ok(Some(ClaimUtxoData {
9✔
2160
            prepared_monitored_utxo: maybe_prepared_mutxo,
9✔
2161
            has_expected_utxo,
9✔
2162
            expected_utxo,
9✔
2163
        }))
9✔
2164
    }
17✔
2165

2166
    /// Return a PoW puzzle with the provided guesser digest.
2167
    async fn pow_puzzle_inner(
14✔
2168
        mut self,
14✔
2169
        guesser_key_after_image: Digest,
14✔
2170
        mut proposal: Block,
14✔
2171
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
14✔
2172
        let latest_block_header = *self.state.lock_guard().await.chain.light_state().header();
14✔
2173

2174
        proposal.set_header_guesser_digest(guesser_key_after_image);
14✔
2175
        let puzzle = ProofOfWorkPuzzle::new(proposal.clone(), latest_block_header);
14✔
2176

2177
        // Record block proposal in case of guesser-success, for later
2178
        // retrieval. But limit number of blocks stored this way.
2179
        let mut state = self.state.lock_guard_mut().await;
14✔
2180
        if state.mining_state.exported_block_proposals.len()
14✔
2181
            >= MAX_NUM_EXPORTED_BLOCK_PROPOSAL_STORED
14✔
2182
        {
2183
            return Err(error::RpcError::ExportedBlockProposalStorageCapacityExceeded);
×
2184
        }
14✔
2185

2186
        state
14✔
2187
            .mining_state
14✔
2188
            .exported_block_proposals
14✔
2189
            .insert(puzzle.id, proposal);
14✔
2190

2191
        Ok(Some(puzzle))
14✔
2192
    }
14✔
2193

2194
    /// get the data_directory for this neptune-core instance
2195
    pub fn data_directory(&self) -> &DataDirectory {
24✔
2196
        &self.data_directory
24✔
2197
    }
24✔
2198
}
2199

2200
impl RPC for NeptuneRPCServer {
2201
    // documented in trait. do not add doc-comment.
2202
    async fn cookie_hint(self, _: context::Context) -> RpcResult<rpc_auth::CookieHint> {
×
2203
        log_slow_scope!(fn_name!());
×
2204

2205
        if self.state.cli().disable_cookie_hint {
×
2206
            Err(error::RpcError::CookieHintDisabled)
×
2207
        } else {
2208
            Ok(rpc_auth::CookieHint {
×
2209
                data_directory: self.data_directory().to_owned(),
×
2210
                network: self.state.cli().network,
×
2211
            })
×
2212
        }
2213
    }
×
2214

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

2219
        Ok(self.state.cli().network)
6✔
2220
    }
6✔
2221

2222
    // documented in trait. do not add doc-comment.
2223
    async fn own_listen_address_for_peers(
1✔
2224
        self,
1✔
2225
        _context: context::Context,
1✔
2226
        token: rpc_auth::Token,
1✔
2227
    ) -> RpcResult<Option<SocketAddr>> {
1✔
2228
        log_slow_scope!(fn_name!());
1✔
2229
        token.auth(&self.valid_tokens)?;
1✔
2230

2231
        let listen_port = self.state.cli().own_listen_port();
1✔
2232
        let listen_for_peers_ip = self.state.cli().listen_addr;
1✔
2233
        Ok(listen_port.map(|port| SocketAddr::new(listen_for_peers_ip, port)))
1✔
2234
    }
1✔
2235

2236
    // documented in trait. do not add doc-comment.
2237
    async fn own_instance_id(
1✔
2238
        self,
1✔
2239
        _context: context::Context,
1✔
2240
        token: rpc_auth::Token,
1✔
2241
    ) -> RpcResult<InstanceId> {
1✔
2242
        log_slow_scope!(fn_name!());
1✔
2243
        token.auth(&self.valid_tokens)?;
1✔
2244

2245
        Ok(self.state.lock_guard().await.net.instance_id)
1✔
2246
    }
1✔
2247

2248
    // documented in trait. do not add doc-comment.
2249
    async fn block_height(
1✔
2250
        self,
1✔
2251
        _: context::Context,
1✔
2252
        token: rpc_auth::Token,
1✔
2253
    ) -> RpcResult<BlockHeight> {
1✔
2254
        log_slow_scope!(fn_name!());
1✔
2255
        token.auth(&self.valid_tokens)?;
1✔
2256

2257
        Ok(self
1✔
2258
            .state
1✔
2259
            .lock_guard()
1✔
2260
            .await
1✔
2261
            .chain
2262
            .light_state()
1✔
2263
            .kernel
2264
            .header
2265
            .height)
2266
    }
1✔
2267

2268
    // documented in trait. do not add doc-comment.
2269
    async fn confirmations(
×
2270
        self,
×
2271
        _: context::Context,
×
2272
        token: rpc_auth::Token,
×
2273
    ) -> RpcResult<Option<BlockHeight>> {
×
2274
        log_slow_scope!(fn_name!());
×
2275
        token.auth(&self.valid_tokens)?;
×
2276

2277
        let guard = self.state.lock_guard().await;
×
2278
        Ok(self.confirmations_internal(&guard).await)
×
2279
    }
×
2280

2281
    // documented in trait. do not add doc-comment.
2282
    async fn utxo_digest(
3✔
2283
        self,
3✔
2284
        _: context::Context,
3✔
2285
        token: rpc_auth::Token,
3✔
2286
        leaf_index: u64,
3✔
2287
    ) -> RpcResult<Option<Digest>> {
3✔
2288
        log_slow_scope!(fn_name!());
3✔
2289
        token.auth(&self.valid_tokens)?;
3✔
2290

2291
        let state = self.state.lock_guard().await;
3✔
2292
        let aocl = &state.chain.archival_state().archival_mutator_set.ams().aocl;
3✔
2293

2294
        Ok(
2295
            match leaf_index > 0 && leaf_index < aocl.num_leafs().await {
3✔
2296
                true => Some(aocl.get_leaf_async(leaf_index).await),
1✔
2297
                false => None,
2✔
2298
            },
2299
        )
2300
    }
3✔
2301

2302
    // documented in trait. do not add doc-comment.
2303
    async fn block_digest(
7✔
2304
        self,
7✔
2305
        _: context::Context,
7✔
2306
        token: rpc_auth::Token,
7✔
2307
        block_selector: BlockSelector,
7✔
2308
    ) -> RpcResult<Option<Digest>> {
7✔
2309
        log_slow_scope!(fn_name!());
7✔
2310
        token.auth(&self.valid_tokens)?;
7✔
2311

2312
        let state = self.state.lock_guard().await;
7✔
2313
        let archival_state = state.chain.archival_state();
7✔
2314
        let Some(digest) = block_selector.as_digest(&state).await else {
7✔
2315
            return Ok(None);
1✔
2316
        };
2317
        // verify the block actually exists
2318
        Ok(archival_state
6✔
2319
            .get_block_header(digest)
6✔
2320
            .await
6✔
2321
            .map(|_| digest))
6✔
2322
    }
7✔
2323

2324
    // documented in trait. do not add doc-comment.
2325
    async fn block_info(
7✔
2326
        self,
7✔
2327
        _: context::Context,
7✔
2328
        token: rpc_auth::Token,
7✔
2329
        block_selector: BlockSelector,
7✔
2330
    ) -> RpcResult<Option<BlockInfo>> {
7✔
2331
        log_slow_scope!(fn_name!());
7✔
2332
        token.auth(&self.valid_tokens)?;
7✔
2333

2334
        let state = self.state.lock_guard().await;
7✔
2335
        let Some(digest) = block_selector.as_digest(&state).await else {
7✔
2336
            return Ok(None);
1✔
2337
        };
2338
        let tip_digest = state.chain.light_state().hash();
6✔
2339
        let archival_state = state.chain.archival_state();
6✔
2340

2341
        let Some(block) = archival_state.get_block(digest).await.unwrap() else {
6✔
2342
            return Ok(None);
2✔
2343
        };
2344
        let is_canonical = archival_state
4✔
2345
            .block_belongs_to_canonical_chain(digest)
4✔
2346
            .await;
4✔
2347

2348
        // sibling blocks are those at the same height, with different digest
2349
        let sibling_blocks = archival_state
4✔
2350
            .block_height_to_block_digests(block.header().height)
4✔
2351
            .await
4✔
2352
            .into_iter()
4✔
2353
            .filter(|d| *d != digest)
4✔
2354
            .collect();
4✔
2355

2356
        Ok(Some(BlockInfo::new(
4✔
2357
            &block,
4✔
2358
            archival_state.genesis_block().hash(),
4✔
2359
            tip_digest,
4✔
2360
            sibling_blocks,
4✔
2361
            is_canonical,
4✔
2362
        )))
4✔
2363
    }
7✔
2364

2365
    // documented in trait. do not add doc-comment.
2366
    async fn public_announcements_in_block(
4✔
2367
        self,
4✔
2368
        _context: tarpc::context::Context,
4✔
2369
        token: rpc_auth::Token,
4✔
2370
        block_selector: BlockSelector,
4✔
2371
    ) -> RpcResult<Option<Vec<PublicAnnouncement>>> {
4✔
2372
        log_slow_scope!(fn_name!());
4✔
2373
        token.auth(&self.valid_tokens)?;
4✔
2374

2375
        let state = self.state.lock_guard().await;
4✔
2376
        let Some(digest) = block_selector.as_digest(&state).await else {
4✔
2377
            return Ok(None);
1✔
2378
        };
2379
        let archival_state = state.chain.archival_state();
3✔
2380
        let Some(block) = archival_state.get_block(digest).await.unwrap() else {
3✔
2381
            return Ok(None);
1✔
2382
        };
2383

2384
        Ok(Some(
2✔
2385
            block.body().transaction_kernel.public_announcements.clone(),
2✔
2386
        ))
2✔
2387
    }
4✔
2388

2389
    // documented in trait. do not add doc-comment.
2390
    async fn block_digests_by_height(
2✔
2391
        self,
2✔
2392
        _: context::Context,
2✔
2393
        token: rpc_auth::Token,
2✔
2394
        height: BlockHeight,
2✔
2395
    ) -> RpcResult<Vec<Digest>> {
2✔
2396
        log_slow_scope!(fn_name!());
2✔
2397
        token.auth(&self.valid_tokens)?;
2✔
2398

2399
        Ok(self
2✔
2400
            .state
2✔
2401
            .lock_guard()
2✔
2402
            .await
2✔
2403
            .chain
2404
            .archival_state()
2✔
2405
            .block_height_to_block_digests(height)
2✔
2406
            .await)
2✔
2407
    }
2✔
2408

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

2419
        let state = self.state.lock_guard().await;
1✔
2420

2421
        let latest_block_digest = state.chain.light_state().hash();
1✔
2422

2423
        Ok(state
1✔
2424
            .chain
1✔
2425
            .archival_state()
1✔
2426
            .get_ancestor_block_digests(latest_block_digest, n)
1✔
2427
            .await)
1✔
2428
    }
1✔
2429

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

2439
        Ok(self
1✔
2440
            .state
1✔
2441
            .lock_guard()
1✔
2442
            .await
1✔
2443
            .net
2444
            .peer_map
2445
            .values()
1✔
2446
            .cloned()
1✔
2447
            .collect())
1✔
2448
    }
1✔
2449

2450
    // documented in trait. do not add doc-comment.
2451
    async fn all_punished_peers(
7✔
2452
        self,
7✔
2453
        _context: tarpc::context::Context,
7✔
2454
        token: rpc_auth::Token,
7✔
2455
    ) -> RpcResult<HashMap<IpAddr, PeerStanding>> {
7✔
2456
        log_slow_scope!(fn_name!());
7✔
2457
        token.auth(&self.valid_tokens)?;
7✔
2458

2459
        let mut sanctions_in_memory = HashMap::default();
7✔
2460

2461
        let global_state = self.state.lock_guard().await;
7✔
2462

2463
        // Get all connected peers
2464
        for (socket_address, peer_info) in &global_state.net.peer_map {
14✔
2465
            if peer_info.standing().is_negative() {
14✔
2466
                sanctions_in_memory.insert(socket_address.ip(), peer_info.standing());
7✔
2467
            }
7✔
2468
        }
2469

2470
        let sanctions_in_db = global_state.net.all_peer_sanctions_in_database();
7✔
2471

2472
        // Combine result for currently connected peers and previously connected peers but
2473
        // use result for currently connected peer if there is an overlap
2474
        let mut all_sanctions = sanctions_in_memory;
7✔
2475
        for (ip_addr, sanction) in sanctions_in_db {
12✔
2476
            if sanction.is_negative() {
5✔
2477
                all_sanctions.entry(ip_addr).or_insert(sanction);
5✔
2478
            }
5✔
2479
        }
2480

2481
        Ok(all_sanctions)
7✔
2482
    }
7✔
2483

2484
    // documented in trait. do not add doc-comment.
2485
    async fn validate_address(
1✔
2486
        self,
1✔
2487
        _ctx: context::Context,
1✔
2488
        token: rpc_auth::Token,
1✔
2489
        address_string: String,
1✔
2490
        network: Network,
1✔
2491
    ) -> RpcResult<Option<ReceivingAddress>> {
1✔
2492
        log_slow_scope!(fn_name!());
1✔
2493
        token.auth(&self.valid_tokens)?;
1✔
2494

2495
        let ret = ReceivingAddress::from_bech32m(&address_string, network).ok();
1✔
2496
        tracing::debug!(
1✔
2497
            "Responding to address validation request of {address_string}: {}",
×
2498
            ret.is_some()
×
2499
        );
2500
        Ok(ret)
1✔
2501
    }
1✔
2502

2503
    // documented in trait. do not add doc-comment.
2504
    async fn validate_amount(
×
2505
        self,
×
2506
        _ctx: context::Context,
×
2507
        token: rpc_auth::Token,
×
2508
        amount_string: String,
×
2509
    ) -> RpcResult<Option<NativeCurrencyAmount>> {
×
2510
        log_slow_scope!(fn_name!());
×
2511
        token.auth(&self.valid_tokens)?;
×
2512

2513
        // parse string
2514
        if let Ok(amt) = NativeCurrencyAmount::coins_from_str(&amount_string) {
×
2515
            Ok(Some(amt))
×
2516
        } else {
2517
            Ok(None)
×
2518
        }
2519
    }
×
2520

2521
    // documented in trait. do not add doc-comment.
2522
    async fn amount_leq_confirmed_available_balance(
×
2523
        self,
×
2524
        _ctx: context::Context,
×
2525
        token: rpc_auth::Token,
×
2526
        amount: NativeCurrencyAmount,
×
2527
    ) -> RpcResult<bool> {
×
2528
        log_slow_scope!(fn_name!());
×
2529
        token.auth(&self.valid_tokens)?;
×
2530

2531
        let gs = self.state.lock_guard().await;
×
2532
        let wallet_status = gs.get_wallet_status_for_tip().await;
×
2533

2534
        let confirmed_available = gs
×
2535
            .wallet_state
×
2536
            .confirmed_available_balance(&wallet_status, Timestamp::now());
×
2537

2538
        // test inequality
2539
        Ok(amount <= confirmed_available)
×
2540
    }
×
2541

2542
    // documented in trait. do not add doc-comment.
2543
    async fn confirmed_available_balance(
9✔
2544
        self,
9✔
2545
        _context: tarpc::context::Context,
9✔
2546
        token: rpc_auth::Token,
9✔
2547
    ) -> RpcResult<NativeCurrencyAmount> {
9✔
2548
        log_slow_scope!(fn_name!());
9✔
2549
        token.auth(&self.valid_tokens)?;
9✔
2550

2551
        let gs = self.state.lock_guard().await;
9✔
2552
        let wallet_status = gs.get_wallet_status_for_tip().await;
9✔
2553

2554
        let confirmed_available = gs
9✔
2555
            .wallet_state
9✔
2556
            .confirmed_available_balance(&wallet_status, Timestamp::now());
9✔
2557

2558
        Ok(confirmed_available)
9✔
2559
    }
9✔
2560

2561
    // documented in trait. do not add doc-comment.
2562
    async fn unconfirmed_available_balance(
×
2563
        self,
×
2564
        _context: tarpc::context::Context,
×
2565
        token: rpc_auth::Token,
×
2566
    ) -> RpcResult<NativeCurrencyAmount> {
×
2567
        log_slow_scope!(fn_name!());
×
2568
        token.auth(&self.valid_tokens)?;
×
2569

2570
        let gs = self.state.lock_guard().await;
×
2571
        let wallet_status = gs.get_wallet_status_for_tip().await;
×
2572

2573
        Ok(gs
×
2574
            .wallet_state
×
2575
            .unconfirmed_available_balance(&wallet_status, Timestamp::now()))
×
2576
    }
×
2577

2578
    // documented in trait. do not add doc-comment.
2579
    async fn wallet_status(
1✔
2580
        self,
1✔
2581
        _context: tarpc::context::Context,
1✔
2582
        token: rpc_auth::Token,
1✔
2583
    ) -> RpcResult<WalletStatus> {
1✔
2584
        log_slow_scope!(fn_name!());
1✔
2585
        token.auth(&self.valid_tokens)?;
1✔
2586

2587
        Ok(self
1✔
2588
            .state
1✔
2589
            .lock_guard()
1✔
2590
            .await
1✔
2591
            .get_wallet_status_for_tip()
1✔
2592
            .await)
1✔
2593
    }
1✔
2594

2595
    async fn num_expected_utxos(
×
2596
        self,
×
2597
        _context: tarpc::context::Context,
×
2598
        token: rpc_auth::Token,
×
2599
    ) -> RpcResult<u64> {
×
2600
        log_slow_scope!(fn_name!());
×
2601
        token.auth(&self.valid_tokens)?;
×
2602

2603
        Ok(self
×
2604
            .state
×
2605
            .lock_guard()
×
2606
            .await
×
2607
            .wallet_state
2608
            .num_expected_utxos()
×
2609
            .await)
×
2610
    }
×
2611

2612
    // documented in trait. do not add doc-comment.
2613
    async fn header(
1✔
2614
        self,
1✔
2615
        _context: tarpc::context::Context,
1✔
2616
        token: rpc_auth::Token,
1✔
2617
        block_selector: BlockSelector,
1✔
2618
    ) -> RpcResult<Option<BlockHeader>> {
1✔
2619
        log_slow_scope!(fn_name!());
1✔
2620
        token.auth(&self.valid_tokens)?;
1✔
2621

2622
        let state = self.state.lock_guard().await;
1✔
2623
        let Some(block_digest) = block_selector.as_digest(&state).await else {
1✔
2624
            return Ok(None);
×
2625
        };
2626
        Ok(state
1✔
2627
            .chain
1✔
2628
            .archival_state()
1✔
2629
            .get_block_header(block_digest)
1✔
2630
            .await)
1✔
2631
    }
1✔
2632

2633
    // documented in trait. do not add doc-comment.
2634
    async fn next_receiving_address(
12✔
2635
        mut self,
12✔
2636
        _context: tarpc::context::Context,
12✔
2637
        token: rpc_auth::Token,
12✔
2638
        key_type: KeyType,
12✔
2639
    ) -> RpcResult<ReceivingAddress> {
12✔
2640
        log_slow_scope!(fn_name!());
12✔
2641
        token.auth(&self.valid_tokens)?;
12✔
2642

2643
        Ok(self
12✔
2644
            .state
12✔
2645
            .api_mut()
12✔
2646
            .wallet_mut()
12✔
2647
            .next_receiving_address(key_type)
12✔
2648
            .await?)
12✔
2649
    }
12✔
2650

2651
    // documented in trait. do not add doc-comment.
2652
    async fn known_keys(
×
2653
        self,
×
2654
        _context: tarpc::context::Context,
×
2655
        token: rpc_auth::Token,
×
2656
    ) -> RpcResult<Vec<BaseSpendingKey>> {
×
2657
        log_slow_scope!(fn_name!());
×
2658
        token.auth(&self.valid_tokens)?;
×
2659

2660
        Ok(self
×
2661
            .state
×
2662
            .lock_guard()
×
2663
            .await
×
2664
            .wallet_state
2665
            .get_all_known_spending_keys()
×
2666
            .collect())
×
2667
    }
×
2668

2669
    // documented in trait. do not add doc-comment.
2670
    async fn known_keys_by_keytype(
×
2671
        self,
×
2672
        _context: tarpc::context::Context,
×
2673
        token: rpc_auth::Token,
×
2674
        key_type: BaseKeyType,
×
2675
    ) -> RpcResult<Vec<BaseSpendingKey>> {
×
2676
        log_slow_scope!(fn_name!());
×
2677
        token.auth(&self.valid_tokens)?;
×
2678

2679
        Ok(self
×
2680
            .state
×
2681
            .lock_guard()
×
2682
            .await
×
2683
            .wallet_state
2684
            .get_known_spending_keys(key_type)
×
2685
            .collect())
×
2686
    }
×
2687

2688
    // documented in trait. do not add doc-comment.
2689
    async fn mempool_tx_count(
1✔
2690
        self,
1✔
2691
        _context: tarpc::context::Context,
1✔
2692
        token: rpc_auth::Token,
1✔
2693
    ) -> RpcResult<usize> {
1✔
2694
        log_slow_scope!(fn_name!());
1✔
2695
        token.auth(&self.valid_tokens)?;
1✔
2696

2697
        Ok(self.state.lock_guard().await.mempool.len())
1✔
2698
    }
1✔
2699

2700
    // documented in trait. do not add doc-comment.
2701
    async fn mempool_size(
1✔
2702
        self,
1✔
2703
        _context: tarpc::context::Context,
1✔
2704
        token: rpc_auth::Token,
1✔
2705
    ) -> RpcResult<usize> {
1✔
2706
        log_slow_scope!(fn_name!());
1✔
2707
        token.auth(&self.valid_tokens)?;
1✔
2708

2709
        Ok(self.state.lock_guard().await.mempool.get_size())
1✔
2710
    }
1✔
2711

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

2721
        let history = self.state.lock_guard().await.get_balance_history().await;
1✔
2722

2723
        // sort
2724
        let mut display_history: Vec<(Digest, BlockHeight, Timestamp, NativeCurrencyAmount)> =
1✔
2725
            history
1✔
2726
                .iter()
1✔
2727
                .map(|(h, t, bh, a)| (*h, *bh, *t, *a))
1✔
2728
                .collect::<Vec<_>>();
1✔
2729
        display_history.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
1✔
2730

2731
        // return
2732
        Ok(display_history)
1✔
2733
    }
1✔
2734

2735
    // documented in trait. do not add doc-comment.
2736
    async fn dashboard_overview_data(
1✔
2737
        self,
1✔
2738
        _context: tarpc::context::Context,
1✔
2739
        token: rpc_auth::Token,
1✔
2740
    ) -> RpcResult<DashBoardOverviewDataFromClient> {
1✔
2741
        log_slow_scope!(fn_name!());
1✔
2742
        token.auth(&self.valid_tokens)?;
1✔
2743

2744
        let now = Timestamp::now();
1✔
2745
        let state = self.state.lock_guard().await;
1✔
2746
        let tip_digest = {
1✔
2747
            log_slow_scope!(fn_name!() + "::hash() tip digest");
1✔
2748
            state.chain.light_state().hash()
1✔
2749
        };
2750
        let tip_header = *state.chain.light_state().header();
1✔
2751
        let syncing = state.net.sync_anchor.is_some();
1✔
2752
        let mempool_size = {
1✔
2753
            log_slow_scope!(fn_name!() + "::mempool.get_size()");
1✔
2754
            state.mempool.get_size()
1✔
2755
        };
2756
        let mempool_total_tx_count = {
1✔
2757
            log_slow_scope!(fn_name!() + "::mempool.len()");
1✔
2758
            state.mempool.len()
1✔
2759
        };
2760
        let mempool_own_tx_count = {
1✔
2761
            log_slow_scope!(fn_name!() + "::mempool.num_own_txs()");
1✔
2762
            state.mempool.num_own_txs()
1✔
2763
        };
2764
        let cpu_temp = None; // disable for now.  call is too slow.
1✔
2765
        let proving_capability = self.state.cli().proving_capability();
1✔
2766

2767
        info!("proving capability: {proving_capability}");
1✔
2768

2769
        let peer_count = Some(state.net.peer_map.len());
1✔
2770
        let max_num_peers = self.state.cli().max_num_peers;
1✔
2771

2772
        let mining_status = Some(state.mining_state.mining_status);
1✔
2773

2774
        let confirmations = {
1✔
2775
            log_slow_scope!(fn_name!() + "::confirmations_internal()");
1✔
2776
            self.confirmations_internal(&state).await
1✔
2777
        };
2778

2779
        let wallet_status = {
1✔
2780
            log_slow_scope!(fn_name!() + "::get_wallet_status_for_tip()");
1✔
2781
            state.get_wallet_status_for_tip().await
1✔
2782
        };
2783
        let wallet_state = &state.wallet_state;
1✔
2784

2785
        let confirmed_available_balance = {
1✔
2786
            log_slow_scope!(fn_name!() + "::confirmed_available_balance()");
1✔
2787
            wallet_state.confirmed_available_balance(&wallet_status, now)
1✔
2788
        };
2789
        let confirmed_total_balance = {
1✔
2790
            log_slow_scope!(fn_name!() + "::confirmed_total_balance()");
1✔
2791
            wallet_state.confirmed_total_balance(&wallet_status)
1✔
2792
        };
2793

2794
        let unconfirmed_available_balance = {
1✔
2795
            log_slow_scope!(fn_name!() + "::unconfirmed_available_balance()");
1✔
2796
            wallet_state.unconfirmed_available_balance(&wallet_status, now)
1✔
2797
        };
2798
        let unconfirmed_total_balance = {
1✔
2799
            log_slow_scope!(fn_name!() + "::unconfirmed_total_balance()");
1✔
2800
            wallet_state.unconfirmed_total_balance(&wallet_status)
1✔
2801
        };
2802

2803
        Ok(DashBoardOverviewDataFromClient {
1✔
2804
            tip_digest,
1✔
2805
            tip_header,
1✔
2806
            syncing,
1✔
2807
            confirmed_available_balance,
1✔
2808
            confirmed_total_balance,
1✔
2809
            unconfirmed_available_balance,
1✔
2810
            unconfirmed_total_balance,
1✔
2811
            mempool_size,
1✔
2812
            mempool_total_tx_count,
1✔
2813
            mempool_own_tx_count,
1✔
2814
            peer_count,
1✔
2815
            max_num_peers,
1✔
2816
            mining_status,
1✔
2817
            proving_capability,
1✔
2818
            confirmations,
1✔
2819
            cpu_temp,
1✔
2820
        })
1✔
2821
    }
1✔
2822

2823
    /******** CHANGE THINGS ********/
2824
    // Locking:
2825
    //   * acquires `global_state_lock` for write
2826
    //
2827
    // documented in trait. do not add doc-comment.
2828
    async fn clear_all_standings(
2✔
2829
        mut self,
2✔
2830
        _: context::Context,
2✔
2831
        token: rpc_auth::Token,
2✔
2832
    ) -> RpcResult<()> {
2✔
2833
        log_slow_scope!(fn_name!());
2✔
2834
        token.auth(&self.valid_tokens)?;
2✔
2835

2836
        let mut global_state_mut = self.state.lock_guard_mut().await;
2✔
2837
        global_state_mut
2✔
2838
            .net
2✔
2839
            .peer_map
2✔
2840
            .iter_mut()
2✔
2841
            .for_each(|(_, peerinfo)| {
4✔
2842
                peerinfo.standing.clear_standing();
4✔
2843
            });
4✔
2844

2845
        // iterates and modifies standing field for all connected peers
2846
        global_state_mut.net.clear_all_standings_in_database().await;
2✔
2847

2848
        Ok(global_state_mut.flush_databases().await?)
2✔
2849
    }
2✔
2850

2851
    // Locking:
2852
    //   * acquires `global_state_lock` for write
2853
    //
2854
    // documented in trait. do not add doc-comment.
2855
    async fn clear_standing_by_ip(
2✔
2856
        mut self,
2✔
2857
        _: context::Context,
2✔
2858
        token: rpc_auth::Token,
2✔
2859
        ip: IpAddr,
2✔
2860
    ) -> RpcResult<()> {
2✔
2861
        log_slow_scope!(fn_name!());
2✔
2862
        token.auth(&self.valid_tokens)?;
2✔
2863

2864
        let mut global_state_mut = self.state.lock_guard_mut().await;
2✔
2865
        global_state_mut
2✔
2866
            .net
2✔
2867
            .peer_map
2✔
2868
            .iter_mut()
2✔
2869
            .for_each(|(socketaddr, peerinfo)| {
4✔
2870
                if socketaddr.ip() == ip {
4✔
2871
                    peerinfo.standing.clear_standing();
1✔
2872
                }
3✔
2873
            });
4✔
2874

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

2878
        Ok(global_state_mut.flush_databases().await?)
2✔
2879
    }
2✔
2880

2881
    // documented in trait. do not add doc-comment.
2882
    async fn spendable_inputs(
×
2883
        self,
×
2884
        _: context::Context,
×
2885
        token: rpc_auth::Token,
×
2886
    ) -> RpcResult<TxInputList> {
×
2887
        log_slow_scope!(fn_name!());
×
2888
        token.auth(&self.valid_tokens)?;
×
2889

2890
        Ok(self.state.api().tx_initiator().spendable_inputs().await)
×
2891
    }
×
2892

2893
    // documented in trait. do not add doc-comment.
2894
    async fn select_spendable_inputs(
×
2895
        self,
×
2896
        _: context::Context,
×
2897
        token: rpc_auth::Token,
×
2898
        policy: InputSelectionPolicy,
×
2899
        spend_amount: NativeCurrencyAmount,
×
2900
    ) -> RpcResult<TxInputList> {
×
2901
        log_slow_scope!(fn_name!());
×
2902
        token.auth(&self.valid_tokens)?;
×
2903

2904
        Ok(self
×
2905
            .state
×
2906
            .api()
×
2907
            .tx_initiator()
×
2908
            .select_spendable_inputs(policy, spend_amount)
×
2909
            .await
×
2910
            .into())
×
2911
    }
×
2912

2913
    // documented in trait. do not add doc-comment.
2914
    async fn generate_tx_outputs(
×
2915
        self,
×
2916
        _: context::Context,
×
2917
        token: rpc_auth::Token,
×
2918
        outputs: Vec<OutputFormat>,
×
2919
    ) -> RpcResult<TxOutputList> {
×
2920
        log_slow_scope!(fn_name!());
×
2921
        token.auth(&self.valid_tokens)?;
×
2922

2923
        Ok(self
×
2924
            .state
×
2925
            .api()
×
2926
            .tx_initiator()
×
2927
            .generate_tx_outputs(outputs)
×
2928
            .await)
×
2929
    }
×
2930

2931
    // documented in trait. do not add doc-comment.
2932
    async fn generate_tx_details(
×
2933
        self,
×
2934
        _: context::Context,
×
2935
        token: rpc_auth::Token,
×
2936
        tx_inputs: TxInputList,
×
2937
        tx_outputs: TxOutputList,
×
2938
        change_policy: ChangePolicy,
×
2939
        fee: NativeCurrencyAmount,
×
2940
    ) -> RpcResult<TransactionDetails> {
×
2941
        log_slow_scope!(fn_name!());
×
2942
        token.auth(&self.valid_tokens)?;
×
2943

2944
        Ok(self
×
2945
            .state
×
2946
            .api()
×
2947
            .tx_initiator()
×
2948
            .generate_tx_details(tx_inputs, tx_outputs, change_policy, fee)
×
2949
            .await?)
×
2950
    }
×
2951

2952
    // documented in trait. do not add doc-comment.
2953
    async fn generate_witness_proof(
×
2954
        self,
×
2955
        _: context::Context,
×
2956
        token: rpc_auth::Token,
×
2957
        tx_details: TransactionDetails,
×
2958
    ) -> RpcResult<TransactionProof> {
×
2959
        log_slow_scope!(fn_name!());
×
2960
        token.auth(&self.valid_tokens)?;
×
2961

2962
        Ok(self
×
2963
            .state
×
2964
            .api()
×
2965
            .tx_initiator()
×
2966
            .generate_witness_proof(Arc::new(tx_details)))
×
2967
    }
×
2968

2969
    // documented in trait. do not add doc-comment.
2970
    async fn assemble_transaction(
×
2971
        self,
×
2972
        _: context::Context,
×
2973
        token: rpc_auth::Token,
×
2974
        transaction_details: TransactionDetails,
×
2975
        transaction_proof: TransactionProof,
×
2976
    ) -> RpcResult<Transaction> {
×
2977
        log_slow_scope!(fn_name!());
×
2978
        token.auth(&self.valid_tokens)?;
×
2979

2980
        Ok(self
×
2981
            .state
×
2982
            .api()
×
2983
            .tx_initiator()
×
2984
            .assemble_transaction(&transaction_details, transaction_proof)?)
×
2985
    }
×
2986

2987
    // documented in trait. do not add doc-comment.
2988
    async fn assemble_transaction_artifacts(
×
2989
        self,
×
2990
        _: context::Context,
×
2991
        token: rpc_auth::Token,
×
2992
        transaction_details: TransactionDetails,
×
2993
        transaction_proof: TransactionProof,
×
2994
    ) -> RpcResult<TxCreationArtifacts> {
×
2995
        log_slow_scope!(fn_name!());
×
2996
        token.auth(&self.valid_tokens)?;
×
2997

2998
        Ok(self
×
2999
            .state
×
3000
            .api()
×
3001
            .tx_initiator()
×
3002
            .assemble_transaction_artifacts(transaction_details, transaction_proof)?)
×
3003
    }
×
3004

3005
    // documented in trait. do not add doc-comment.
3006
    async fn record_and_broadcast_transaction(
×
3007
        mut self,
×
3008
        _: context::Context,
×
3009
        token: rpc_auth::Token,
×
3010
        tx_artifacts: TxCreationArtifacts,
×
3011
    ) -> RpcResult<()> {
×
3012
        log_slow_scope!(fn_name!());
×
3013
        token.auth(&self.valid_tokens)?;
×
3014

3015
        Ok(self
×
3016
            .state
×
3017
            .api_mut()
×
3018
            .tx_initiator_mut()
×
3019
            .record_and_broadcast_transaction(&tx_artifacts)
×
3020
            .await?)
×
3021
    }
×
3022

3023
    // documented in trait. do not add doc-comment.
3024
    async fn send(
23✔
3025
        mut self,
23✔
3026
        _ctx: context::Context,
23✔
3027
        token: rpc_auth::Token,
23✔
3028
        outputs: Vec<OutputFormat>,
23✔
3029
        change_policy: ChangePolicy,
23✔
3030
        fee: NativeCurrencyAmount,
23✔
3031
    ) -> RpcResult<TxCreationArtifacts> {
23✔
3032
        log_slow_scope!(fn_name!());
23✔
3033
        token.auth(&self.valid_tokens)?;
23✔
3034

3035
        Ok(self
23✔
3036
            .state
23✔
3037
            .api_mut()
23✔
3038
            .tx_sender_mut()
23✔
3039
            .send(outputs, change_policy, fee, Timestamp::now())
23✔
3040
            .await?)
23✔
3041
    }
23✔
3042

3043
    // documented in trait. do not add doc-comment.
3044
    async fn upgrade_tx_proof(
×
3045
        mut self,
×
3046
        _ctx: context::Context,
×
3047
        token: rpc_auth::Token,
×
3048
        transaction_id: TransactionKernelId,
×
3049
        transaction_proof: TransactionProof,
×
3050
    ) -> RpcResult<()> {
×
3051
        log_slow_scope!(fn_name!());
×
3052
        token.auth(&self.valid_tokens)?;
×
3053

3054
        Ok(self
×
3055
            .state
×
3056
            .api_mut()
×
3057
            .tx_initiator_mut()
×
3058
            .upgrade_tx_proof(transaction_id, transaction_proof)
×
3059
            .await?)
×
3060
    }
×
3061

3062
    // documented in trait. do not add doc-comment.
3063
    async fn proof_type(
×
3064
        self,
×
3065
        _ctx: context::Context,
×
3066
        token: rpc_auth::Token,
×
3067
        txid: TransactionKernelId,
×
3068
    ) -> RpcResult<TransactionProofType> {
×
3069
        log_slow_scope!(fn_name!());
×
3070
        token.auth(&self.valid_tokens)?;
×
3071

3072
        Ok(self.state.api().tx_initiator().proof_type(txid).await?)
×
3073
    }
×
3074

3075
    // // documented in trait. do not add doc-comment.
3076
    async fn claim_utxo(
17✔
3077
        mut self,
17✔
3078
        _ctx: context::Context,
17✔
3079
        token: rpc_auth::Token,
17✔
3080
        encrypted_utxo_notification: String,
17✔
3081
        max_search_depth: Option<u64>,
17✔
3082
    ) -> RpcResult<bool> {
17✔
3083
        log_slow_scope!(fn_name!());
17✔
3084
        token.auth(&self.valid_tokens)?;
17✔
3085

3086
        let claim_data = self
17✔
3087
            .claim_utxo_inner(encrypted_utxo_notification, max_search_depth)
17✔
3088
            .await?;
17✔
3089

3090
        let Some(claim_data) = claim_data else {
17✔
3091
            // UTXO has already been claimed by wallet
3092
            warn!("UTXO notification of amount was already received. Not adding again.");
8✔
3093
            return Ok(false);
8✔
3094
        };
3095

3096
        let expected_utxo_was_new = !claim_data.has_expected_utxo;
9✔
3097
        self.state
9✔
3098
            .lock_guard_mut()
9✔
3099
            .await
9✔
3100
            .wallet_state
3101
            .claim_utxo(claim_data)
9✔
3102
            .await
9✔
3103
            .map_err(error::ClaimError::from)?;
9✔
3104

3105
        Ok(expected_utxo_was_new)
9✔
3106
    }
17✔
3107

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

3113
        // 1. Send shutdown message to main
3114
        let response = self
1✔
3115
            .rpc_server_to_main_tx
1✔
3116
            .send(RPCServerToMain::Shutdown)
1✔
3117
            .await;
1✔
3118

3119
        // 2. Send acknowledgement to client.
3120
        Ok(response.is_ok())
1✔
3121
    }
1✔
3122

3123
    // documented in trait. do not add doc-comment.
3124
    async fn clear_mempool(
×
3125
        self,
×
3126
        _context: tarpc::context::Context,
×
3127
        token: rpc_auth::Token,
×
3128
    ) -> RpcResult<()> {
×
3129
        log_slow_scope!(fn_name!());
×
3130
        token.auth(&self.valid_tokens)?;
×
3131

3132
        let _ = self
×
3133
            .rpc_server_to_main_tx
×
3134
            .send(RPCServerToMain::ClearMempool)
×
3135
            .await;
×
3136

3137
        Ok(())
×
3138
    }
×
3139

3140
    async fn pause_miner(
1✔
3141
        self,
1✔
3142
        _context: tarpc::context::Context,
1✔
3143
        token: rpc_auth::Token,
1✔
3144
    ) -> RpcResult<()> {
1✔
3145
        log_slow_scope!(fn_name!());
1✔
3146
        token.auth(&self.valid_tokens)?;
1✔
3147

3148
        if self.state.cli().mine() {
1✔
3149
            let _ = self
×
3150
                .rpc_server_to_main_tx
×
3151
                .send(RPCServerToMain::PauseMiner)
×
3152
                .await;
×
3153
        } else {
3154
            info!("Cannot pause miner since it was never started");
1✔
3155
        }
3156
        Ok(())
1✔
3157
    }
1✔
3158

3159
    // documented in trait. do not add doc-comment.
3160
    async fn restart_miner(
1✔
3161
        self,
1✔
3162
        _context: tarpc::context::Context,
1✔
3163
        token: rpc_auth::Token,
1✔
3164
    ) -> RpcResult<()> {
1✔
3165
        log_slow_scope!(fn_name!());
1✔
3166
        token.auth(&self.valid_tokens)?;
1✔
3167

3168
        if self.state.cli().mine() {
1✔
3169
            let _ = self
×
3170
                .rpc_server_to_main_tx
×
3171
                .send(RPCServerToMain::RestartMiner)
×
3172
                .await;
×
3173
        } else {
3174
            info!("Cannot restart miner since it was never started");
1✔
3175
        }
3176
        Ok(())
1✔
3177
    }
1✔
3178

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

3189
        Ok(self
×
3190
            .state
×
3191
            .api_mut()
×
3192
            .regtest_mut()
×
3193
            .mine_blocks_to_wallet(n_blocks)
×
3194
            .await?)
×
3195
    }
×
3196

3197
    // documented in trait. do not add doc-comment.
3198
    async fn provide_pow_solution(
6✔
3199
        self,
6✔
3200
        _context: tarpc::context::Context,
6✔
3201
        token: rpc_auth::Token,
6✔
3202
        nonce: Digest,
6✔
3203
        proposal_id: Digest,
6✔
3204
    ) -> RpcResult<bool> {
6✔
3205
        log_slow_scope!(fn_name!());
6✔
3206
        token.auth(&self.valid_tokens)?;
6✔
3207

3208
        // Find proposal from list of exported proposals.
3209
        let Some(mut proposal) = self
6✔
3210
            .state
6✔
3211
            .lock_guard()
6✔
3212
            .await
6✔
3213
            .mining_state
3214
            .exported_block_proposals
3215
            .get(&proposal_id)
6✔
3216
            .map(|x| x.to_owned())
6✔
3217
        else {
3218
            warn!(
2✔
3219
                "Got claimed PoW solution but no challenge was known. \
×
3220
                Did solution come in too late?"
×
3221
            );
3222
            return Ok(false);
2✔
3223
        };
3224

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

3228
        proposal.set_header_nonce(nonce);
4✔
3229
        let threshold = latest_block_header.difficulty.target();
4✔
3230
        let solution_digest = proposal.hash();
4✔
3231
        if solution_digest > threshold {
4✔
3232
            warn!(
2✔
3233
                "Got claimed PoW solution but PoW threshold was not met.\n\
×
3234
            Claimed solution: {solution_digest};\nthreshold: {threshold}"
×
3235
            );
3236
            return Ok(false);
2✔
3237
        }
2✔
3238

3239
        // No time to waste! Inform main_loop!
3240
        let solution = Box::new(proposal);
2✔
3241
        let _ = self
2✔
3242
            .rpc_server_to_main_tx
2✔
3243
            .send(RPCServerToMain::ProofOfWorkSolution(solution))
2✔
3244
            .await;
2✔
3245

3246
        Ok(true)
2✔
3247
    }
6✔
3248

3249
    // documented in trait. do not add doc-comment.
3250
    async fn prune_abandoned_monitored_utxos(
1✔
3251
        mut self,
1✔
3252
        _context: tarpc::context::Context,
1✔
3253
        token: rpc_auth::Token,
1✔
3254
    ) -> RpcResult<usize> {
1✔
3255
        const DEFAULT_MUTXO_PRUNE_DEPTH: usize = 200;
3256

3257
        log_slow_scope!(fn_name!());
1✔
3258
        token.auth(&self.valid_tokens)?;
1✔
3259

3260
        let mut global_state_mut = self.state.lock_guard_mut().await;
1✔
3261

3262
        let prune_count_res = global_state_mut
1✔
3263
            .prune_abandoned_monitored_utxos(DEFAULT_MUTXO_PRUNE_DEPTH)
1✔
3264
            .await;
1✔
3265

3266
        global_state_mut
1✔
3267
            .flush_databases()
1✔
3268
            .await
1✔
3269
            .expect("flushed DBs");
1✔
3270

3271
        match prune_count_res {
1✔
3272
            Ok(prune_count) => {
1✔
3273
                info!("Marked {prune_count} monitored UTXOs as abandoned");
1✔
3274
                Ok(prune_count)
1✔
3275
            }
3276
            Err(err) => {
×
3277
                error!("Pruning monitored UTXOs failed with error: {err}");
×
3278
                Ok(0)
×
3279
            }
3280
        }
3281
    }
1✔
3282

3283
    // documented in trait. do not add doc-comment.
3284
    async fn list_own_coins(
×
3285
        self,
×
3286
        _context: ::tarpc::context::Context,
×
3287
        token: rpc_auth::Token,
×
3288
    ) -> RpcResult<Vec<CoinWithPossibleTimeLock>> {
×
3289
        log_slow_scope!(fn_name!());
×
3290
        token.auth(&self.valid_tokens)?;
×
3291

3292
        let state = self.state.lock_guard().await;
×
3293
        let tip = state.chain.light_state();
×
3294
        let tip_hash = tip.hash();
×
3295
        let tip_msa = tip.mutator_set_accumulator_after();
×
3296

3297
        Ok(state
×
3298
            .wallet_state
×
3299
            .get_all_own_coins_with_possible_timelocks(&tip_msa, tip_hash)
×
3300
            .await)
×
3301
    }
×
3302

3303
    // documented in trait. do not add doc-comment.
3304
    async fn cpu_temp(
1✔
3305
        self,
1✔
3306
        _context: tarpc::context::Context,
1✔
3307
        token: rpc_auth::Token,
1✔
3308
    ) -> RpcResult<Option<f32>> {
1✔
3309
        log_slow_scope!(fn_name!());
1✔
3310
        token.auth(&self.valid_tokens)?;
1✔
3311

3312
        Ok(Self::cpu_temp_inner())
1✔
3313
    }
1✔
3314

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

3324
        let Some(proposal) = self
2✔
3325
            .state
2✔
3326
            .lock_guard()
2✔
3327
            .await
2✔
3328
            .mining_state
3329
            .block_proposal
3330
            .map(|x| x.to_owned())
2✔
3331
        else {
3332
            return Ok(None);
1✔
3333
        };
3334

3335
        let guesser_key_after_image = self
1✔
3336
            .state
1✔
3337
            .lock_guard()
1✔
3338
            .await
1✔
3339
            .wallet_state
3340
            .wallet_entropy
3341
            .guesser_spending_key(proposal.header().prev_block_digest)
1✔
3342
            .after_image();
1✔
3343

3344
        self.pow_puzzle_inner(guesser_key_after_image, proposal)
1✔
3345
            .await
1✔
3346
    }
2✔
3347

3348
    // documented in trait. do not add doc-comment.
3349
    async fn pow_puzzle_external_key(
14✔
3350
        self,
14✔
3351
        _context: tarpc::context::Context,
14✔
3352
        token: rpc_auth::Token,
14✔
3353
        guesser_digest: Digest,
14✔
3354
    ) -> RpcResult<Option<ProofOfWorkPuzzle>> {
14✔
3355
        log_slow_scope!(fn_name!());
14✔
3356
        token.auth(&self.valid_tokens)?;
14✔
3357

3358
        let Some(proposal) = self
14✔
3359
            .state
14✔
3360
            .lock_guard()
14✔
3361
            .await
14✔
3362
            .mining_state
3363
            .block_proposal
3364
            .map(|x| x.to_owned())
14✔
3365
        else {
3366
            return Ok(None);
1✔
3367
        };
3368

3369
        self.pow_puzzle_inner(guesser_digest, proposal).await
13✔
3370
    }
14✔
3371

3372
    // documented in trait. do not add doc-comment.
3373
    async fn block_intervals(
1✔
3374
        self,
1✔
3375
        _context: tarpc::context::Context,
1✔
3376
        token: rpc_auth::Token,
1✔
3377
        last_block: BlockSelector,
1✔
3378
        max_num_blocks: Option<usize>,
1✔
3379
    ) -> RpcResult<Option<Vec<(u64, u64)>>> {
1✔
3380
        log_slow_scope!(fn_name!());
1✔
3381
        token.auth(&self.valid_tokens)?;
1✔
3382

3383
        let state = self.state.lock_guard().await;
1✔
3384
        let Some(last_block) = last_block.as_digest(&state).await else {
1✔
3385
            return Ok(None);
×
3386
        };
3387
        let mut intervals = vec![];
1✔
3388
        let mut current = state
1✔
3389
            .chain
1✔
3390
            .archival_state()
1✔
3391
            .get_block_header(last_block)
1✔
3392
            .await
1✔
3393
            .expect("If digest can be found, block header should also be known");
1✔
3394
        let mut parent = state
1✔
3395
            .chain
1✔
3396
            .archival_state()
1✔
3397
            .get_block_header(current.prev_block_digest)
1✔
3398
            .await;
1✔
3399

3400
        // Exclude genesis since it was not mined. So block interval 0-->1
3401
        // is not included.
3402
        while parent.is_some()
1✔
3403
            && !parent.unwrap().height.is_genesis()
×
3404
            && max_num_blocks.is_none_or(|max_num| max_num > intervals.len())
×
3405
        {
3406
            let parent_ = parent.unwrap();
×
3407
            let interval = current.timestamp.to_millis() - parent_.timestamp.to_millis();
×
3408
            let block_height: u64 = current.height.into();
×
3409
            intervals.push((block_height, interval));
×
3410
            current = parent_;
×
3411
            parent = state
×
3412
                .chain
×
3413
                .archival_state()
×
3414
                .get_block_header(current.prev_block_digest)
×
3415
                .await;
×
3416
        }
3417

3418
        Ok(Some(intervals))
1✔
3419
    }
1✔
3420

3421
    async fn block_difficulties(
1✔
3422
        self,
1✔
3423
        _context: tarpc::context::Context,
1✔
3424
        token: rpc_auth::Token,
1✔
3425
        last_block: BlockSelector,
1✔
3426
        max_num_blocks: Option<usize>,
1✔
3427
    ) -> RpcResult<Vec<(u64, Difficulty)>> {
1✔
3428
        log_slow_scope!(fn_name!());
1✔
3429
        token.auth(&self.valid_tokens)?;
1✔
3430

3431
        let state = self.state.lock_guard().await;
1✔
3432
        let last_block = last_block.as_digest(&state).await;
1✔
3433
        let Some(last_block) = last_block else {
1✔
3434
            return Ok(vec![]);
×
3435
        };
3436

3437
        let mut difficulties = vec![];
1✔
3438

3439
        let mut current = state
1✔
3440
            .chain
1✔
3441
            .archival_state()
1✔
3442
            .get_block_header(last_block)
1✔
3443
            .await;
1✔
3444
        while current.is_some()
2✔
3445
            && max_num_blocks.is_none_or(|max_num| max_num >= difficulties.len())
1✔
3446
        {
3447
            let current_ = current.unwrap();
1✔
3448
            let height: u64 = current_.height.into();
1✔
3449
            difficulties.push((height, current_.difficulty));
1✔
3450
            current = state
1✔
3451
                .chain
1✔
3452
                .archival_state()
1✔
3453
                .get_block_header(current_.prev_block_digest)
1✔
3454
                .await;
1✔
3455
        }
3456

3457
        Ok(difficulties)
1✔
3458
    }
1✔
3459

3460
    // documented in trait. do not add doc-comment.
3461
    async fn broadcast_all_mempool_txs(
1✔
3462
        self,
1✔
3463
        _context: tarpc::context::Context,
1✔
3464
        token: rpc_auth::Token,
1✔
3465
    ) -> RpcResult<()> {
1✔
3466
        log_slow_scope!(fn_name!());
1✔
3467
        token.auth(&self.valid_tokens)?;
1✔
3468

3469
        // If this sending fails, it means `main_loop` is no longer running,
3470
        // and node is crashed. No reason to log anything additional.
3471
        let _ = self
1✔
3472
            .rpc_server_to_main_tx
1✔
3473
            .send(RPCServerToMain::BroadcastMempoolTransactions)
1✔
3474
            .await;
1✔
3475

3476
        Ok(())
1✔
3477
    }
1✔
3478

3479
    // documented in trait. do not add doc-comment.
3480
    async fn mempool_overview(
1✔
3481
        self,
1✔
3482
        _context: ::tarpc::context::Context,
1✔
3483
        token: rpc_auth::Token,
1✔
3484
        start_index: usize,
1✔
3485
        number: usize,
1✔
3486
    ) -> RpcResult<Vec<MempoolTransactionInfo>> {
1✔
3487
        log_slow_scope!(fn_name!());
1✔
3488
        token.auth(&self.valid_tokens)?;
1✔
3489

3490
        let global_state = self.state.lock_guard().await;
1✔
3491
        let mempool_txkids = global_state
1✔
3492
            .mempool
1✔
3493
            .get_sorted_iter()
1✔
3494
            .skip(start_index)
1✔
3495
            .take(number)
1✔
3496
            .map(|(txkid, _)| txkid)
1✔
3497
            .collect_vec();
1✔
3498

3499
        let (incoming, outgoing): (HashMap<_, _>, HashMap<_, _>) = {
1✔
3500
            let (incoming_iter, outgoing_iter) =
1✔
3501
                global_state.wallet_state.mempool_balance_updates();
1✔
3502
            (incoming_iter.collect(), outgoing_iter.collect())
1✔
3503
        };
1✔
3504

3505
        let tip_msah = global_state
1✔
3506
            .chain
1✔
3507
            .light_state()
1✔
3508
            .mutator_set_accumulator_after()
1✔
3509
            .hash();
1✔
3510

3511
        let mempool_transactions = mempool_txkids
1✔
3512
            .iter()
1✔
3513
            .filter_map(|id| {
1✔
3514
                let mut mptxi = global_state
×
3515
                    .mempool
×
3516
                    .get(*id)
×
3517
                    .map(|tx| (MempoolTransactionInfo::from(tx), tx.kernel.mutator_set_hash))
×
3518
                    .map(|(mptxi, tx_msah)| {
×
3519
                        if tx_msah == tip_msah {
×
3520
                            mptxi.synced()
×
3521
                        } else {
3522
                            mptxi
×
3523
                        }
3524
                    });
×
3525
                if mptxi.is_some() {
×
3526
                    if let Some(pos_effect) = incoming.get(id) {
×
3527
                        mptxi = Some(mptxi.unwrap().with_positive_effect_on_balance(*pos_effect));
×
3528
                    }
×
3529
                    if let Some(neg_effect) = outgoing.get(id) {
×
3530
                        mptxi = Some(mptxi.unwrap().with_negative_effect_on_balance(*neg_effect));
×
3531
                    }
×
3532
                }
×
3533

3534
                mptxi
×
3535
            })
×
3536
            .collect_vec();
1✔
3537

3538
        Ok(mempool_transactions)
1✔
3539
    }
1✔
3540
}
3541

3542
pub mod error {
3543
    use super::*;
3544

3545
    /// enumerates possible rpc api errors
3546
    #[derive(Debug, thiserror::Error, Serialize, Deserialize)]
3547
    #[non_exhaustive]
3548
    pub enum RpcError {
3549
        // auth error
3550
        #[error("auth error: {0}")]
3551
        Auth(#[from] rpc_auth::error::AuthError),
3552

3553
        // catch-all error, eg for anyhow errors
3554
        #[error("rpc call failed: {0}")]
3555
        Failed(String),
3556

3557
        // API specific error variants.
3558
        #[error("cookie hints are disabled on this node")]
3559
        CookieHintDisabled,
3560

3561
        #[error("capacity to store exported block proposals exceeded")]
3562
        ExportedBlockProposalStorageCapacityExceeded,
3563

3564
        #[error("create transaction error: {0}")]
3565
        CreateTxError(String),
3566

3567
        #[error("upgrade proof error: {0}")]
3568
        UpgradeProofError(String),
3569

3570
        #[error("send error: {0}")]
3571
        SendError(String),
3572

3573
        #[error("regtest error: {0}")]
3574
        RegTestError(String),
3575

3576
        #[error("wallet error: {0}")]
3577
        WalletError(String),
3578

3579
        #[error("claim error: {0}")]
3580
        ClaimError(String),
3581
    }
3582

3583
    impl From<tx_initiation::error::CreateTxError> for RpcError {
UNCOV
3584
        fn from(err: tx_initiation::error::CreateTxError) -> Self {
×
UNCOV
3585
            RpcError::CreateTxError(err.to_string())
×
3586
        }
×
3587
    }
3588

3589
    impl From<tx_initiation::error::UpgradeProofError> for RpcError {
UNCOV
3590
        fn from(err: tx_initiation::error::UpgradeProofError) -> Self {
×
UNCOV
3591
            RpcError::UpgradeProofError(err.to_string())
×
UNCOV
3592
        }
×
3593
    }
3594

3595
    impl From<tx_initiation::error::SendError> for RpcError {
3596
        fn from(err: tx_initiation::error::SendError) -> Self {
11✔
3597
            RpcError::SendError(err.to_string())
11✔
3598
        }
11✔
3599
    }
3600

3601
    impl From<api::regtest::error::RegTestError> for RpcError {
UNCOV
3602
        fn from(err: api::regtest::error::RegTestError) -> Self {
×
UNCOV
3603
            RpcError::RegTestError(err.to_string())
×
UNCOV
3604
        }
×
3605
    }
3606

3607
    impl From<api::wallet::error::WalletError> for RpcError {
UNCOV
3608
        fn from(err: api::wallet::error::WalletError) -> Self {
×
NEW
3609
            RpcError::WalletError(err.to_string())
×
3610
        }
×
3611
    }
3612

3613
    impl From<ClaimError> for RpcError {
UNCOV
3614
        fn from(err: ClaimError) -> Self {
×
UNCOV
3615
            RpcError::ClaimError(err.to_string())
×
UNCOV
3616
        }
×
3617
    }
3618

3619
    // convert anyhow::Error to an RpcError::Failed.
3620
    // note that anyhow Error is not serializable.
3621
    impl From<anyhow::Error> for RpcError {
UNCOV
3622
        fn from(e: anyhow::Error) -> Self {
×
UNCOV
3623
            Self::Failed(e.to_string())
×
UNCOV
3624
        }
×
3625
    }
3626

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

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

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

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

3651
#[cfg(test)]
3652
#[cfg_attr(coverage_nightly, coverage(off))]
3653
mod tests {
3654
    use anyhow::Result;
3655
    use macro_rules_attr::apply;
3656
    use num_traits::One;
3657
    use num_traits::Zero;
3658
    use rand::rngs::StdRng;
3659
    use rand::Rng;
3660
    use rand::SeedableRng;
3661
    use strum::IntoEnumIterator;
3662
    use tracing_test::traced_test;
3663

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

3682
    async fn test_rpc_server(
3683
        network: Network,
3684
        wallet_entropy: WalletEntropy,
3685
        peer_count: u8,
3686
        cli: cli_args::Args,
3687
    ) -> NeptuneRPCServer {
3688
        let global_state_lock =
3689
            mock_genesis_global_state(network, peer_count, wallet_entropy, cli).await;
3690

3691
        let data_directory = unit_test_data_directory(network).unwrap();
3692

3693
        let valid_tokens: Vec<rpc_auth::Token> = vec![rpc_auth::Cookie::try_new(&data_directory)
3694
            .await
3695
            .unwrap()
3696
            .into()];
3697

3698
        let rpc_to_main_tx = global_state_lock.rpc_server_to_main_tx();
3699

3700
        NeptuneRPCServer::new(
3701
            global_state_lock,
3702
            rpc_to_main_tx,
3703
            data_directory,
3704
            valid_tokens,
3705
        )
3706
    }
3707

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

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

3732
        Ok(())
3733
    }
3734

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

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

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

3850
        // let transaction_timestamp = network.launch_date();
3851
        // let proving_capability = rpc_server.state.cli().proving_capability();
3852
        let my_output: OutputFormat = (own_receiving_address, NativeCurrencyAmount::one()).into();
3853
        let _ = rpc_server
3854
            .clone()
3855
            .send(
3856
                ctx,
3857
                token,
3858
                vec![my_output],
3859
                ChangePolicy::ExactChange,
3860
                NativeCurrencyAmount::one(),
3861
            )
3862
            .await;
3863

3864
        // .send_to_many_inner(
3865
        //     ctx,
3866
        //     vec![],
3867
        //     (
3868
        //         UtxoNotificationMedium::OffChain,
3869
        //         UtxoNotificationMedium::OffChain,
3870
        //     ),
3871
        //     NativeCurrencyAmount::one(),
3872
        //     transaction_timestamp,
3873
        //     proving_capability,
3874
        // )
3875
        // .await;
3876
        let _ = rpc_server.clone().pause_miner(ctx, token).await;
3877
        let _ = rpc_server.clone().restart_miner(ctx, token).await;
3878
        let _ = rpc_server
3879
            .clone()
3880
            .prune_abandoned_monitored_utxos(ctx, token)
3881
            .await;
3882
        let _ = rpc_server.shutdown(ctx, token).await;
3883

3884
        Ok(())
3885
    }
3886

3887
    #[traced_test]
3888
    #[apply(shared_tokio_runtime)]
3889
    async fn balance_is_zero_at_init() -> Result<()> {
3890
        // Verify that a wallet not receiving a premine is empty at startup
3891
        let rpc_server = test_rpc_server(
3892
            Network::Alpha,
3893
            WalletEntropy::new_random(),
3894
            2,
3895
            cli_args::Args::default(),
3896
        )
3897
        .await;
3898
        let token = cookie_token(&rpc_server).await;
3899
        let balance = rpc_server
3900
            .confirmed_available_balance(context::current(), token)
3901
            .await?;
3902
        assert!(balance.is_zero());
3903

3904
        Ok(())
3905
    }
3906

3907
    #[expect(clippy::shadow_unrelated)]
3908
    #[traced_test]
3909
    #[apply(shared_tokio_runtime)]
3910
    async fn clear_ip_standing_test() -> Result<()> {
3911
        let mut rpc_server = test_rpc_server(
3912
            Network::Alpha,
3913
            WalletEntropy::new_random(),
3914
            2,
3915
            cli_args::Args::default(),
3916
        )
3917
        .await;
3918
        let token = cookie_token(&rpc_server).await;
3919
        let rpc_request_context = context::current();
3920
        let (peer_address0, peer_address1) = {
3921
            let global_state = rpc_server.state.lock_guard().await;
3922

3923
            (
3924
                global_state.net.peer_map.values().collect::<Vec<_>>()[0].connected_address(),
3925
                global_state.net.peer_map.values().collect::<Vec<_>>()[1].connected_address(),
3926
            )
3927
        };
3928

3929
        // Verify that sanctions list is empty
3930
        let punished_peers_startup = rpc_server
3931
            .clone()
3932
            .all_punished_peers(rpc_request_context, token)
3933
            .await?;
3934
        assert!(
3935
            punished_peers_startup.is_empty(),
3936
            "Sanctions list must be empty at startup"
3937
        );
3938

3939
        // sanction both
3940
        let (standing0, standing1) = {
3941
            let mut global_state_mut = rpc_server.state.lock_guard_mut().await;
3942

3943
            global_state_mut
3944
                .net
3945
                .peer_map
3946
                .entry(peer_address0)
3947
                .and_modify(|p| {
3948
                    p.standing
3949
                        .sanction(PeerSanction::Negative(
3950
                            NegativePeerSanction::DifferentGenesis,
3951
                        ))
3952
                        .unwrap_err();
3953
                });
3954
            global_state_mut
3955
                .net
3956
                .peer_map
3957
                .entry(peer_address1)
3958
                .and_modify(|p| {
3959
                    p.standing
3960
                        .sanction(PeerSanction::Negative(
3961
                            NegativePeerSanction::DifferentGenesis,
3962
                        ))
3963
                        .unwrap_err();
3964
                });
3965
            let standing_0 = global_state_mut.net.peer_map[&peer_address0].standing;
3966
            let standing_1 = global_state_mut.net.peer_map[&peer_address1].standing;
3967
            (standing_0, standing_1)
3968
        };
3969

3970
        // Verify expected sanctions reading
3971
        let punished_peers_from_memory = rpc_server
3972
            .clone()
3973
            .all_punished_peers(rpc_request_context, token)
3974
            .await?;
3975
        assert_eq!(
3976
            2,
3977
            punished_peers_from_memory.len(),
3978
            "Punished list must have two elements after sanctionings"
3979
        );
3980

3981
        {
3982
            let mut global_state_mut = rpc_server.state.lock_guard_mut().await;
3983

3984
            global_state_mut
3985
                .net
3986
                .write_peer_standing_on_decrease(peer_address0.ip(), standing0)
3987
                .await;
3988
            global_state_mut
3989
                .net
3990
                .write_peer_standing_on_decrease(peer_address1.ip(), standing1)
3991
                .await;
3992
        }
3993

3994
        // Verify expected sanctions reading, after DB-write
3995
        let punished_peers_from_memory_and_db = rpc_server
3996
            .clone()
3997
            .all_punished_peers(rpc_request_context, token)
3998
            .await?;
3999
        assert_eq!(
4000
            2,
4001
            punished_peers_from_memory_and_db.len(),
4002
            "Punished list must have to elements after sanctionings and after DB write"
4003
        );
4004

4005
        // Verify expected initial conditions
4006
        {
4007
            let global_state = rpc_server.state.lock_guard().await;
4008
            let standing0 = global_state
4009
                .net
4010
                .get_peer_standing_from_database(peer_address0.ip())
4011
                .await;
4012
            assert_ne!(0, standing0.unwrap().standing);
4013
            assert_ne!(None, standing0.unwrap().latest_punishment);
4014
            let peer_standing_1 = global_state
4015
                .net
4016
                .get_peer_standing_from_database(peer_address1.ip())
4017
                .await;
4018
            assert_ne!(0, peer_standing_1.unwrap().standing);
4019
            assert_ne!(None, peer_standing_1.unwrap().latest_punishment);
4020
            drop(global_state);
4021

4022
            // Clear standing of #0
4023
            rpc_server
4024
                .clone()
4025
                .clear_standing_by_ip(rpc_request_context, token, peer_address0.ip())
4026
                .await?;
4027
        }
4028

4029
        // Verify expected resulting conditions in database
4030
        {
4031
            let global_state = rpc_server.state.lock_guard().await;
4032
            let standing0 = global_state
4033
                .net
4034
                .get_peer_standing_from_database(peer_address0.ip())
4035
                .await;
4036
            assert_eq!(0, standing0.unwrap().standing);
4037
            assert_eq!(None, standing0.unwrap().latest_punishment);
4038
            let standing1 = global_state
4039
                .net
4040
                .get_peer_standing_from_database(peer_address1.ip())
4041
                .await;
4042
            assert_ne!(0, standing1.unwrap().standing);
4043
            assert_ne!(None, standing1.unwrap().latest_punishment);
4044

4045
            // Verify expected resulting conditions in peer map
4046
            let standing0_from_memory = global_state.net.peer_map[&peer_address0].clone();
4047
            assert_eq!(0, standing0_from_memory.standing.standing);
4048
            let standing1_from_memory = global_state.net.peer_map[&peer_address1].clone();
4049
            assert_ne!(0, standing1_from_memory.standing.standing);
4050
        }
4051

4052
        // Verify expected sanctions reading, after one forgiveness
4053
        let punished_list_after_one_clear = rpc_server
4054
            .clone()
4055
            .all_punished_peers(rpc_request_context, token)
4056
            .await?;
4057
        assert!(
4058
            punished_list_after_one_clear.len().is_one(),
4059
            "Punished list must have to elements after sanctionings and after DB write"
4060
        );
4061

4062
        Ok(())
4063
    }
4064

4065
    #[expect(clippy::shadow_unrelated)]
4066
    #[traced_test]
4067
    #[apply(shared_tokio_runtime)]
4068
    async fn clear_all_standings_test() -> Result<()> {
4069
        // Create initial conditions
4070
        let mut rpc_server = test_rpc_server(
4071
            Network::Alpha,
4072
            WalletEntropy::new_random(),
4073
            2,
4074
            cli_args::Args::default(),
4075
        )
4076
        .await;
4077
        let token = cookie_token(&rpc_server).await;
4078
        let mut state = rpc_server.state.lock_guard_mut().await;
4079
        let peer_address0 = state.net.peer_map.values().collect::<Vec<_>>()[0].connected_address();
4080
        let peer_address1 = state.net.peer_map.values().collect::<Vec<_>>()[1].connected_address();
4081

4082
        // sanction both peers
4083
        let (standing0, standing1) = {
4084
            state.net.peer_map.entry(peer_address0).and_modify(|p| {
4085
                p.standing
4086
                    .sanction(PeerSanction::Negative(
4087
                        NegativePeerSanction::DifferentGenesis,
4088
                    ))
4089
                    .unwrap_err();
4090
            });
4091
            state.net.peer_map.entry(peer_address1).and_modify(|p| {
4092
                p.standing
4093
                    .sanction(PeerSanction::Negative(
4094
                        NegativePeerSanction::DifferentGenesis,
4095
                    ))
4096
                    .unwrap_err();
4097
            });
4098
            (
4099
                state.net.peer_map[&peer_address0].standing,
4100
                state.net.peer_map[&peer_address1].standing,
4101
            )
4102
        };
4103

4104
        state
4105
            .net
4106
            .write_peer_standing_on_decrease(peer_address0.ip(), standing0)
4107
            .await;
4108
        state
4109
            .net
4110
            .write_peer_standing_on_decrease(peer_address1.ip(), standing1)
4111
            .await;
4112

4113
        drop(state);
4114

4115
        // Verify expected initial conditions
4116
        {
4117
            let peer_standing0 = rpc_server
4118
                .state
4119
                .lock_guard_mut()
4120
                .await
4121
                .net
4122
                .get_peer_standing_from_database(peer_address0.ip())
4123
                .await;
4124
            assert_ne!(0, peer_standing0.unwrap().standing);
4125
            assert_ne!(None, peer_standing0.unwrap().latest_punishment);
4126
        }
4127

4128
        {
4129
            let peer_standing1 = rpc_server
4130
                .state
4131
                .lock_guard_mut()
4132
                .await
4133
                .net
4134
                .get_peer_standing_from_database(peer_address1.ip())
4135
                .await;
4136
            assert_ne!(0, peer_standing1.unwrap().standing);
4137
            assert_ne!(None, peer_standing1.unwrap().latest_punishment);
4138
        }
4139

4140
        // Verify expected reading through an RPC call
4141
        let rpc_request_context = context::current();
4142
        let after_two_sanctions = rpc_server
4143
            .clone()
4144
            .all_punished_peers(rpc_request_context, token)
4145
            .await?;
4146
        assert_eq!(2, after_two_sanctions.len());
4147

4148
        // Clear standing of both by clearing all standings
4149
        rpc_server
4150
            .clone()
4151
            .clear_all_standings(rpc_request_context, token)
4152
            .await?;
4153

4154
        let state = rpc_server.state.lock_guard().await;
4155

4156
        // Verify expected resulting conditions in database
4157
        {
4158
            let peer_standing_0 = state
4159
                .net
4160
                .get_peer_standing_from_database(peer_address0.ip())
4161
                .await;
4162
            assert_eq!(0, peer_standing_0.unwrap().standing);
4163
            assert_eq!(None, peer_standing_0.unwrap().latest_punishment);
4164
        }
4165

4166
        {
4167
            let peer_still_standing_1 = state
4168
                .net
4169
                .get_peer_standing_from_database(peer_address1.ip())
4170
                .await;
4171
            assert_eq!(0, peer_still_standing_1.unwrap().standing);
4172
            assert_eq!(None, peer_still_standing_1.unwrap().latest_punishment);
4173
        }
4174

4175
        // Verify expected resulting conditions in peer map
4176
        {
4177
            let peer_standing_0_from_memory = state.net.peer_map[&peer_address0].clone();
4178
            assert_eq!(0, peer_standing_0_from_memory.standing.standing);
4179
        }
4180

4181
        {
4182
            let peer_still_standing_1_from_memory = state.net.peer_map[&peer_address1].clone();
4183
            assert_eq!(0, peer_still_standing_1_from_memory.standing.standing);
4184
        }
4185

4186
        // Verify expected reading through an RPC call
4187
        let after_global_forgiveness = rpc_server
4188
            .clone()
4189
            .all_punished_peers(rpc_request_context, token)
4190
            .await?;
4191
        assert!(after_global_forgiveness.is_empty());
4192

4193
        Ok(())
4194
    }
4195

4196
    #[traced_test]
4197
    #[apply(shared_tokio_runtime)]
4198
    async fn utxo_digest_test() {
4199
        let rpc_server = test_rpc_server(
4200
            Network::Alpha,
4201
            WalletEntropy::new_random(),
4202
            2,
4203
            cli_args::Args::default(),
4204
        )
4205
        .await;
4206
        let token = cookie_token(&rpc_server).await;
4207
        let aocl_leaves = rpc_server
4208
            .state
4209
            .lock_guard()
4210
            .await
4211
            .chain
4212
            .archival_state()
4213
            .archival_mutator_set
4214
            .ams()
4215
            .aocl
4216
            .num_leafs()
4217
            .await;
4218

4219
        debug_assert!(aocl_leaves > 0);
4220

4221
        assert!(rpc_server
4222
            .clone()
4223
            .utxo_digest(context::current(), token, aocl_leaves - 1)
4224
            .await
4225
            .unwrap()
4226
            .is_some());
4227

4228
        assert!(rpc_server
4229
            .utxo_digest(context::current(), token, aocl_leaves)
4230
            .await
4231
            .unwrap()
4232
            .is_none());
4233
    }
4234

4235
    #[traced_test]
4236
    #[apply(shared_tokio_runtime)]
4237
    async fn block_info_test() {
4238
        let network = Network::RegTest;
4239
        let rpc_server = test_rpc_server(
4240
            network,
4241
            WalletEntropy::new_random(),
4242
            2,
4243
            cli_args::Args::default(),
4244
        )
4245
        .await;
4246
        let token = cookie_token(&rpc_server).await;
4247
        let global_state = rpc_server.state.lock_guard().await;
4248
        let ctx = context::current();
4249

4250
        let genesis_hash = global_state.chain.archival_state().genesis_block().hash();
4251
        let tip_hash = global_state.chain.light_state().hash();
4252

4253
        let genesis_block_info = BlockInfo::new(
4254
            global_state.chain.archival_state().genesis_block(),
4255
            genesis_hash,
4256
            tip_hash,
4257
            vec![],
4258
            global_state
4259
                .chain
4260
                .archival_state()
4261
                .block_belongs_to_canonical_chain(genesis_hash)
4262
                .await,
4263
        );
4264

4265
        assert!(
4266
            genesis_block_info.num_public_announcements.is_zero(),
4267
            "Genesis block contains no public announcements. Block info must reflect that."
4268
        );
4269

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

4282
        // should find genesis block by Genesis selector
4283
        assert_eq!(
4284
            genesis_block_info,
4285
            rpc_server
4286
                .clone()
4287
                .block_info(ctx, token, BlockSelector::Genesis)
4288
                .await
4289
                .unwrap()
4290
                .unwrap()
4291
        );
4292

4293
        // should find latest/tip block by Tip selector
4294
        assert_eq!(
4295
            tip_block_info,
4296
            rpc_server
4297
                .clone()
4298
                .block_info(ctx, token, BlockSelector::Tip)
4299
                .await
4300
                .unwrap()
4301
                .unwrap()
4302
        );
4303

4304
        // should find genesis block by Height selector
4305
        assert_eq!(
4306
            genesis_block_info,
4307
            rpc_server
4308
                .clone()
4309
                .block_info(ctx, token, BlockSelector::Height(BlockHeight::from(0u64)))
4310
                .await
4311
                .unwrap()
4312
                .unwrap()
4313
        );
4314

4315
        // should find genesis block by Digest selector
4316
        assert_eq!(
4317
            genesis_block_info,
4318
            rpc_server
4319
                .clone()
4320
                .block_info(ctx, token, BlockSelector::Digest(genesis_hash))
4321
                .await
4322
                .unwrap()
4323
                .unwrap()
4324
        );
4325

4326
        // should not find any block when Height selector is u64::Max
4327
        assert!(rpc_server
4328
            .clone()
4329
            .block_info(
4330
                ctx,
4331
                token,
4332
                BlockSelector::Height(BlockHeight::from(u64::MAX))
4333
            )
4334
            .await
4335
            .unwrap()
4336
            .is_none());
4337

4338
        // should not find any block when Digest selector is Digest::default()
4339
        assert!(rpc_server
4340
            .clone()
4341
            .block_info(ctx, token, BlockSelector::Digest(Digest::default()))
4342
            .await
4343
            .unwrap()
4344
            .is_none());
4345
    }
4346

4347
    #[traced_test]
4348
    #[apply(shared_tokio_runtime)]
4349
    async fn public_announcements_in_block_test() {
4350
        let network = Network::Main;
4351
        let mut rpc_server = test_rpc_server(
4352
            network,
4353
            WalletEntropy::new_random(),
4354
            2,
4355
            cli_args::Args::default(),
4356
        )
4357
        .await;
4358
        let mut rng = rand::rng();
4359
        let num_public_announcements_block1 = 7;
4360
        let num_inputs = 0;
4361
        let num_outputs = 2;
4362
        let tx_block1 = pseudorandom_transaction_kernel(
4363
            rng.random(),
4364
            num_inputs,
4365
            num_outputs,
4366
            num_public_announcements_block1,
4367
        );
4368
        let tx_block1 = Transaction {
4369
            kernel: tx_block1,
4370
            proof: TransactionProof::invalid(),
4371
        };
4372
        let block1 = invalid_block_with_transaction(&Block::genesis(network), tx_block1);
4373
        rpc_server.state.set_new_tip(block1.clone()).await.unwrap();
4374

4375
        let token = cookie_token(&rpc_server).await;
4376
        let ctx = context::current();
4377
        let block1_public_announcements = rpc_server
4378
            .clone()
4379
            .public_announcements_in_block(ctx, token, BlockSelector::Height(1u64.into()))
4380
            .await
4381
            .unwrap()
4382
            .unwrap();
4383
        assert_eq!(
4384
            block1.body().transaction_kernel.public_announcements,
4385
            block1_public_announcements,
4386
            "Must return expected public announcements"
4387
        );
4388
        assert_eq!(
4389
            num_public_announcements_block1,
4390
            block1_public_announcements.len(),
4391
            "Must return expected number of public announcements"
4392
        );
4393

4394
        let genesis_block_public_announcements = rpc_server
4395
            .clone()
4396
            .public_announcements_in_block(ctx, token, BlockSelector::Height(0u64.into()))
4397
            .await
4398
            .unwrap()
4399
            .unwrap();
4400
        assert!(
4401
            genesis_block_public_announcements.is_empty(),
4402
            "Genesis block has no public announements"
4403
        );
4404

4405
        assert!(
4406
            rpc_server
4407
                .public_announcements_in_block(ctx, token, BlockSelector::Height(2u64.into()))
4408
                .await
4409
                .unwrap()
4410
                .is_none(),
4411
            "Public announcements in unknown block must return None"
4412
        );
4413
    }
4414

4415
    #[traced_test]
4416
    #[apply(shared_tokio_runtime)]
4417
    async fn block_digest_test() {
4418
        let network = Network::RegTest;
4419
        let rpc_server = test_rpc_server(
4420
            network,
4421
            WalletEntropy::new_random(),
4422
            2,
4423
            cli_args::Args::default(),
4424
        )
4425
        .await;
4426
        let token = cookie_token(&rpc_server).await;
4427
        let global_state = rpc_server.state.lock_guard().await;
4428
        let ctx = context::current();
4429

4430
        let genesis_hash = Block::genesis(network).hash();
4431

4432
        // should find genesis block by Genesis selector
4433
        assert_eq!(
4434
            genesis_hash,
4435
            rpc_server
4436
                .clone()
4437
                .block_digest(ctx, token, BlockSelector::Genesis)
4438
                .await
4439
                .unwrap()
4440
                .unwrap()
4441
        );
4442

4443
        // should find latest/tip block by Tip selector
4444
        assert_eq!(
4445
            global_state.chain.light_state().hash(),
4446
            rpc_server
4447
                .clone()
4448
                .block_digest(ctx, token, BlockSelector::Tip)
4449
                .await
4450
                .unwrap()
4451
                .unwrap()
4452
        );
4453

4454
        // should find genesis block by Height selector
4455
        assert_eq!(
4456
            genesis_hash,
4457
            rpc_server
4458
                .clone()
4459
                .block_digest(ctx, token, BlockSelector::Height(BlockHeight::from(0u64)))
4460
                .await
4461
                .unwrap()
4462
                .unwrap()
4463
        );
4464

4465
        // should find genesis block by Digest selector
4466
        assert_eq!(
4467
            genesis_hash,
4468
            rpc_server
4469
                .clone()
4470
                .block_digest(ctx, token, BlockSelector::Digest(genesis_hash))
4471
                .await
4472
                .unwrap()
4473
                .unwrap()
4474
        );
4475

4476
        // should not find any block when Height selector is u64::Max
4477
        assert!(rpc_server
4478
            .clone()
4479
            .block_digest(
4480
                ctx,
4481
                token,
4482
                BlockSelector::Height(BlockHeight::from(u64::MAX))
4483
            )
4484
            .await
4485
            .unwrap()
4486
            .is_none());
4487

4488
        // should not find any block when Digest selector is Digest::default()
4489
        assert!(rpc_server
4490
            .clone()
4491
            .block_digest(ctx, token, BlockSelector::Digest(Digest::default()))
4492
            .await
4493
            .unwrap()
4494
            .is_none());
4495
    }
4496

4497
    #[traced_test]
4498
    #[apply(shared_tokio_runtime)]
4499
    async fn getting_temperature_doesnt_crash_test() {
4500
        // On your local machine, this should return a temperature but in CI,
4501
        // the RPC call returns `None`, so we only verify that the call doesn't
4502
        // crash the host machine, we don't verify that any value is returned.
4503
        let rpc_server = test_rpc_server(
4504
            Network::Alpha,
4505
            WalletEntropy::new_random(),
4506
            2,
4507
            cli_args::Args::default(),
4508
        )
4509
        .await;
4510
        let token = cookie_token(&rpc_server).await;
4511
        let _current_server_temperature = rpc_server
4512
            .cpu_temp(context::current(), token)
4513
            .await
4514
            .unwrap();
4515
    }
4516

4517
    #[traced_test]
4518
    #[apply(shared_tokio_runtime)]
4519
    async fn cannot_initiate_transaction_if_notx_flag_is_set() {
4520
        let network = Network::Main;
4521
        let ctx = context::current();
4522
        let mut rng = rand::rng();
4523
        let address = GenerationSpendingKey::derive_from_seed(rng.random()).to_address();
4524
        let amount = NativeCurrencyAmount::coins(rng.random_range(0..10));
4525

4526
        // set flag on, verify non-initiation
4527
        let cli_on = cli_args::Args {
4528
            no_transaction_initiation: true,
4529
            ..Default::default()
4530
        };
4531

4532
        let rpc_server = test_rpc_server(network, WalletEntropy::new_random(), 2, cli_on).await;
4533
        let token = cookie_token(&rpc_server).await;
4534

4535
        let output: OutputFormat = (address.into(), amount).into();
4536
        assert!(rpc_server
4537
            .clone()
4538
            .send(
4539
                ctx,
4540
                token,
4541
                vec![output],
4542
                ChangePolicy::ExactChange,
4543
                NativeCurrencyAmount::zero()
4544
            )
4545
            .await
4546
            .is_err());
4547
    }
4548

4549
    mod pow_puzzle_tests {
4550
        use rand::random;
4551
        use tasm_lib::twenty_first::math::other::random_elements;
4552

4553
        use super::*;
4554
        use crate::mine_loop::fast_kernel_mast_hash;
4555
        use crate::models::state::block_proposal::BlockProposal;
4556
        use crate::models::state::wallet::address::hash_lock_key::HashLockKey;
4557
        use crate::tests::shared::invalid_empty_block;
4558

4559
        #[test]
4560
        fn pow_puzzle_is_consistent_with_block_hash() {
4561
            let network = Network::Main;
4562
            let genesis = Block::genesis(network);
4563
            let mut block1 = invalid_empty_block(&genesis);
4564
            let hash_lock_key = HashLockKey::from_preimage(random());
4565
            block1.set_header_guesser_digest(hash_lock_key.after_image());
4566

4567
            let guess_challenge = ProofOfWorkPuzzle::new(block1.clone(), *genesis.header());
4568
            assert_eq!(guess_challenge.prev_block, genesis.hash());
4569

4570
            let nonce = random();
4571
            let resulting_block_hash = fast_kernel_mast_hash(
4572
                guess_challenge.kernel_auth_path,
4573
                guess_challenge.header_auth_path,
4574
                nonce,
4575
            );
4576

4577
            block1.set_header_nonce(nonce);
4578

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4804
        #[traced_test]
4805
        #[apply(shared_tokio_runtime)]
4806
        async fn claim_utxo_owned_before_confirmed() -> Result<()> {
4807
            worker::claim_utxo_owned(false, false).await
4808
        }
4809

4810
        #[traced_test]
4811
        #[apply(shared_tokio_runtime)]
4812
        async fn claim_utxo_owned_after_confirmed() -> Result<()> {
4813
            worker::claim_utxo_owned(true, false).await
4814
        }
4815

4816
        #[traced_test]
4817
        #[apply(shared_tokio_runtime)]
4818
        async fn claim_utxo_owned_after_confirmed_and_after_spent() -> Result<()> {
4819
            worker::claim_utxo_owned(true, true).await
4820
        }
4821

4822
        #[traced_test]
4823
        #[apply(shared_tokio_runtime)]
4824
        async fn claim_utxo_unowned_before_confirmed() -> Result<()> {
4825
            worker::claim_utxo_unowned(false).await
4826
        }
4827

4828
        #[traced_test]
4829
        #[apply(shared_tokio_runtime)]
4830
        async fn claim_utxo_unowned_after_confirmed() -> Result<()> {
4831
            worker::claim_utxo_unowned(true).await
4832
        }
4833

4834
        mod worker {
4835
            use cli_args::Args;
4836

4837
            use super::*;
4838
            use crate::models::state::tx_proving_capability::TxProvingCapability;
4839
            use crate::tests::shared::invalid_block_with_transaction;
4840
            use crate::tests::shared::invalid_empty_block;
4841

4842
            pub(super) async fn claim_utxo_unowned(claim_after_confirmed: bool) -> Result<()> {
4843
                let network = Network::Main;
4844

4845
                // bob's node
4846
                let (pay_to_bob_outputs, bob_rpc_server, bob_token) = {
4847
                    let rpc_server =
4848
                        test_rpc_server(network, WalletEntropy::new_random(), 2, Args::default())
4849
                            .await;
4850
                    let token = cookie_token(&rpc_server).await;
4851

4852
                    let receiving_address_generation = rpc_server
4853
                        .clone()
4854
                        .next_receiving_address(context::current(), token, KeyType::Generation)
4855
                        .await?;
4856
                    let receiving_address_symmetric = rpc_server
4857
                        .clone()
4858
                        .next_receiving_address(context::current(), token, KeyType::Symmetric)
4859
                        .await?;
4860

4861
                    let pay_to_bob_outputs: Vec<OutputFormat> = [
4862
                        (
4863
                            receiving_address_generation,
4864
                            NativeCurrencyAmount::coins(1),
4865
                            UtxoNotificationMedium::OffChain,
4866
                        ),
4867
                        (
4868
                            receiving_address_symmetric,
4869
                            NativeCurrencyAmount::coins(2),
4870
                            UtxoNotificationMedium::OffChain,
4871
                        ),
4872
                    ]
4873
                    .into_iter()
4874
                    .map(|o| o.into())
4875
                    .collect();
4876

4877
                    (pay_to_bob_outputs, rpc_server, token)
4878
                };
4879

4880
                // alice's node
4881
                let (blocks, alice_to_bob_utxo_notifications, bob_amount) = {
4882
                    let wallet_entropy = WalletEntropy::new_random();
4883
                    let cli_args = cli_args::Args {
4884
                        tx_proving_capability: Some(TxProvingCapability::ProofCollection),
4885
                        ..Default::default()
4886
                    };
4887
                    let mut rpc_server =
4888
                        test_rpc_server(network, wallet_entropy.clone(), 2, cli_args).await;
4889
                    let token = cookie_token(&rpc_server).await;
4890

4891
                    let genesis_block = Block::genesis(network);
4892
                    let mut blocks = vec![];
4893

4894
                    let fee = NativeCurrencyAmount::zero();
4895
                    let bob_amount: NativeCurrencyAmount = pay_to_bob_outputs
4896
                        .iter()
4897
                        .map(|o| o.native_currency_amount())
4898
                        .sum();
4899

4900
                    // Mine block 1 to get some coins
4901

4902
                    let cb_key = wallet_entropy.nth_generation_spending_key(0);
4903
                    let (block1, composer_expected_utxos) =
4904
                        make_mock_block(&genesis_block, None, cb_key, Default::default()).await;
4905
                    blocks.push(block1.clone());
4906

4907
                    rpc_server
4908
                        .state
4909
                        .set_new_self_composed_tip(block1.clone(), composer_expected_utxos)
4910
                        .await
4911
                        .unwrap();
4912

4913
                    let tx_artifacts = rpc_server
4914
                        .clone()
4915
                        .send(
4916
                            context::current(),
4917
                            token,
4918
                            pay_to_bob_outputs,
4919
                            ChangePolicy::recover_to_next_unused_key(
4920
                                KeyType::Symmetric,
4921
                                UtxoNotificationMedium::OffChain,
4922
                            ),
4923
                            fee,
4924
                        )
4925
                        .await
4926
                        .unwrap();
4927

4928
                    let block2 = invalid_block_with_transaction(
4929
                        &block1,
4930
                        tx_artifacts.transaction.clone().into(),
4931
                    );
4932
                    let block3 = invalid_empty_block(&block2);
4933

4934
                    // mine two blocks, the first will include the transaction
4935
                    blocks.push(block2);
4936
                    blocks.push(block3);
4937

4938
                    // note: change-policy uses off-chain, so alice will have an
4939
                    // off-chain notificatin also.  So it is important to use
4940
                    // unowned_offchain_notifications() when retrieving those
4941
                    // intended for bob.
4942

4943
                    (
4944
                        blocks,
4945
                        tx_artifacts.unowned_offchain_notifications(),
4946
                        bob_amount,
4947
                    )
4948
                };
4949

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

4954
                    state.set_new_tip(blocks[0].clone()).await?;
4955

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

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

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

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

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

5026
                Ok(())
5027
            }
5028

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

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

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

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

5065
                let pay_to_self_outputs: Vec<OutputFormat> = [
5066
                    (
5067
                        bob_gen_addr,
5068
                        NativeCurrencyAmount::coins(5),
5069
                        UtxoNotificationMedium::OffChain,
5070
                    ),
5071
                    (
5072
                        bob_sym_addr,
5073
                        NativeCurrencyAmount::coins(6),
5074
                        UtxoNotificationMedium::OffChain,
5075
                    ),
5076
                ]
5077
                .into_iter()
5078
                .map(|o| o.into())
5079
                .collect();
5080

5081
                let fee = NativeCurrencyAmount::coins(2);
5082
                let tx_artifacts = bob
5083
                    .clone()
5084
                    .send(
5085
                        context::current(),
5086
                        bob_token,
5087
                        pay_to_self_outputs.clone(),
5088
                        ChangePolicy::recover_to_next_unused_key(
5089
                            KeyType::Symmetric,
5090
                            UtxoNotificationMedium::OffChain,
5091
                        ),
5092
                        fee,
5093
                    )
5094
                    .await
5095
                    .unwrap();
5096

5097
                // alice mines 2 more blocks.  block2 confirms the sent tx.
5098
                let block2 = invalid_block_with_transaction(
5099
                    &block1,
5100
                    tx_artifacts.transaction.clone().into(),
5101
                );
5102
                let block3 = invalid_empty_block(&block2);
5103

5104
                if claim_after_mined {
5105
                    // bob applies the blocks before claiming utxos.
5106
                    bob.state.set_new_tip(block2.clone()).await?;
5107
                    bob.state.set_new_tip(block3.clone()).await?;
5108

5109
                    if spent {
5110
                        // Send entire liquid balance somewhere else
5111
                        let another_address = WalletEntropy::new_random()
5112
                            .nth_generation_spending_key(0)
5113
                            .to_address();
5114
                        let output: OutputFormat = (
5115
                            another_address.into(),
5116
                            NativeCurrencyAmount::coins(62),
5117
                            UtxoNotificationMedium::OffChain,
5118
                        )
5119
                            .into();
5120
                        let spending_tx_artifacts = bob
5121
                            .clone()
5122
                            .send(
5123
                                context::current(),
5124
                                bob_token,
5125
                                vec![output],
5126
                                ChangePolicy::exact_change(),
5127
                                NativeCurrencyAmount::zero(),
5128
                            )
5129
                            .await
5130
                            .unwrap();
5131
                        let block4 = invalid_block_with_transaction(
5132
                            &block3,
5133
                            spending_tx_artifacts.transaction.clone().into(),
5134
                        );
5135
                        bob.state.set_new_tip(block4.clone()).await?;
5136
                    }
5137
                }
5138

5139
                for offchain_notification in tx_artifacts.owned_offchain_notifications() {
5140
                    bob.clone()
5141
                        .claim_utxo(
5142
                            context::current(),
5143
                            bob_token,
5144
                            offchain_notification.ciphertext,
5145
                            None,
5146
                        )
5147
                        .await?;
5148
                }
5149

5150
                assert_eq!(
5151
                    vec![
5152
                        NativeCurrencyAmount::coins(64), // liquid composer reward, block 1
5153
                        NativeCurrencyAmount::coins(64), // illiquid composer reward, block 1
5154
                        NativeCurrencyAmount::coins(5),  // claimed via generation addr
5155
                        NativeCurrencyAmount::coins(6),  // claimed via symmetric addr
5156
                        // 51 = (64 - 5 - 6 - 2 (fee))
5157
                        NativeCurrencyAmount::coins(51) // change (symmetric addr)
5158
                    ],
5159
                    bob.state
5160
                        .lock_guard()
5161
                        .await
5162
                        .wallet_state
5163
                        .wallet_db
5164
                        .expected_utxos()
5165
                        .get_all()
5166
                        .await
5167
                        .iter()
5168
                        .map(|eu| eu.utxo.get_native_currency_amount())
5169
                        .collect_vec()
5170
                );
5171

5172
                if !claim_after_mined {
5173
                    // bob hasn't applied blocks 2,3. liquid balance should be 64
5174
                    assert_eq!(
5175
                        NativeCurrencyAmount::coins(64),
5176
                        bob.clone()
5177
                            .confirmed_available_balance(context::current(), bob_token)
5178
                            .await?,
5179
                    );
5180
                    // bob applies the blocks after claiming utxos.
5181
                    bob.state.set_new_tip(block2).await?;
5182
                    bob.state.set_new_tip(block3).await?;
5183
                }
5184

5185
                if spent {
5186
                    assert!(bob
5187
                        .confirmed_available_balance(context::current(), bob_token)
5188
                        .await?
5189
                        .is_zero(),);
5190
                } else {
5191
                    // final liquid balance should be 62.
5192
                    // +64 composer liquid
5193
                    // +64 composer timelocked (not counted)
5194
                    // -64 composer liquid spent
5195
                    // +5 self-send via Generation
5196
                    // +6 self-send via Symmetric
5197
                    // +51   change (less fee == 2)
5198
                    assert_eq!(
5199
                        NativeCurrencyAmount::coins(62),
5200
                        bob.confirmed_available_balance(context::current(), bob_token)
5201
                            .await?,
5202
                    );
5203
                }
5204
                Ok(())
5205
            }
5206
        }
5207
    }
5208

5209
    mod send_tests {
5210
        use super::*;
5211
        use crate::rpc_server::error::RpcError;
5212
        use crate::tests::shared::mine_block_to_wallet_invalid_block_proof;
5213

5214
        #[traced_test]
5215
        #[apply(shared_tokio_runtime)]
5216
        async fn send_to_many_n_outputs() {
5217
            let mut rng = StdRng::seed_from_u64(1815);
5218
            let network = Network::Main;
5219
            let cli_args = cli_args::Args {
5220
                tx_proving_capability: Some(TxProvingCapability::ProofCollection),
5221
                ..Default::default()
5222
            };
5223
            let rpc_server = test_rpc_server(
5224
                network,
5225
                WalletEntropy::new_pseudorandom(rng.random()),
5226
                2,
5227
                cli_args,
5228
            )
5229
            .await;
5230
            let token = cookie_token(&rpc_server).await;
5231

5232
            let ctx = context::current();
5233
            // let timestamp = network.launch_date() + Timestamp::days(1);
5234
            let own_address = rpc_server
5235
                .clone()
5236
                .next_receiving_address(ctx, token, KeyType::Generation)
5237
                .await
5238
                .unwrap();
5239
            let elem: OutputFormat = (
5240
                own_address.clone(),
5241
                NativeCurrencyAmount::zero(),
5242
                UtxoNotificationMedium::OffChain,
5243
            )
5244
                .into();
5245
            let outputs = std::iter::repeat(elem);
5246
            let fee = NativeCurrencyAmount::zero();
5247

5248
            // note: we can only perform 2 iters, else we bump into send rate-limit (per block)
5249
            for i in 5..7 {
5250
                let result = rpc_server
5251
                    .clone()
5252
                    .send(
5253
                        ctx,
5254
                        token,
5255
                        outputs.clone().take(i).collect(),
5256
                        ChangePolicy::ExactChange,
5257
                        fee,
5258
                    )
5259
                    .await;
5260
                // .send_to_many_inner(
5261
                //     ctx,
5262
                //     outputs.clone().take(i).collect(),
5263
                //     (
5264
                //         UtxoNotificationMedium::OffChain,
5265
                //         UtxoNotificationMedium::OffChain,
5266
                //     ),
5267
                //     fee,
5268
                //     timestamp,
5269
                //     TxProvingCapability::PrimitiveWitness,
5270
                // )
5271
                // .await;
5272
                assert!(result.is_ok());
5273
            }
5274
        }
5275

5276
        /// sends a tx with two outputs: one self, one external, for each key type
5277
        /// that accepts incoming UTXOs.
5278
        #[traced_test]
5279
        #[apply(shared_tokio_runtime)]
5280
        async fn send_to_many_test() -> Result<()> {
5281
            for recipient_key_type in KeyType::all_types() {
5282
                worker::send_to_many(recipient_key_type).await?;
5283
            }
5284
            Ok(())
5285
        }
5286

5287
        /// checks that the sending rate limit kicks in after 2 tx are sent.
5288
        /// note: rate-limit only applies below block 25000
5289
        #[traced_test]
5290
        #[apply(shared_tokio_runtime)]
5291
        async fn send_rate_limit() -> Result<()> {
5292
            let mut rng = StdRng::seed_from_u64(1815);
5293
            let network = Network::Main;
5294
            let cli_args = cli_args::Args {
5295
                tx_proving_capability: Some(TxProvingCapability::SingleProof),
5296
                ..Default::default()
5297
            };
5298
            let mut rpc_server =
5299
                test_rpc_server(network, WalletEntropy::devnet_wallet(), 2, cli_args).await;
5300

5301
            let ctx = context::current();
5302
            let token = cookie_token(&rpc_server).await;
5303
            let timestamp = network.launch_date() + Timestamp::months(7);
5304

5305
            // obtain some funds, so we have two inputs available.
5306
            mine_block_to_wallet_invalid_block_proof(&mut rpc_server.state, Some(timestamp))
5307
                .await?;
5308

5309
            let address: ReceivingAddress = GenerationSpendingKey::derive_from_seed(rng.random())
5310
                .to_address()
5311
                .into();
5312
            let amount = NativeCurrencyAmount::coins(rng.random_range(0..2));
5313
            let fee = NativeCurrencyAmount::coins(1);
5314

5315
            let output: OutputFormat = (address, amount, UtxoNotificationMedium::OnChain).into();
5316
            let outputs = vec![output];
5317

5318
            for i in 0..10 {
5319
                let result = rpc_server
5320
                    .clone()
5321
                    .send(ctx, token, outputs.clone(), ChangePolicy::Burn, fee)
5322
                    .await;
5323

5324
                // any attempts after the 2nd send should result in RateLimit error.
5325
                match i {
5326
                    0..2 => assert!(result.is_ok()),
5327
                    _ => assert!(matches!(
5328
                        result,
5329
                        Err(RpcError::SendError(s)) if s.contains("Send rate limit reached")
5330
                    )),
5331
                }
5332
            }
5333

5334
            Ok(())
5335
        }
5336

5337
        mod worker {
5338
            use super::*;
5339
            use crate::models::state::wallet::address::generation_address::GenerationReceivingAddress;
5340
            use crate::models::state::wallet::address::symmetric_key::SymmetricKey;
5341
            use crate::models::state::wallet::address::SpendingKey;
5342

5343
            // sends a tx with two outputs: one self, one external.
5344
            //
5345
            // input: recipient_key_type: can be symmetric or generation.
5346
            //
5347
            // Steps:
5348
            // --- Init.  Basics ---
5349
            // --- Init.  get wallet spending key ---
5350
            // --- Init.  generate a block, with coinbase going to our wallet ---
5351
            // --- Init.  append the block to blockchain ---
5352
            // --- Setup. generate an output that our wallet cannot claim. ---
5353
            // --- Setup. generate an output that our wallet can claim. ---
5354
            // --- Setup. assemble outputs and fee ---
5355
            // --- Store: store num expected utxo before spend ---
5356
            // --- Operation: perform send_to_many
5357
            // --- Test: bech32m serialize/deserialize roundtrip.
5358
            // --- Test: verify op returns a value.
5359
            // --- Test: verify expected_utxos.len() has increased by 2.
5360
            pub(super) async fn send_to_many(recipient_key_type: KeyType) -> Result<()> {
5361
                info!("recipient_key_type: {}", recipient_key_type);
5362

5363
                // --- Init.  Basics ---
5364
                let mut rng = StdRng::seed_from_u64(1814);
5365
                let network = Network::Main;
5366
                let cli_args = cli_args::Args {
5367
                    tx_proving_capability: Some(TxProvingCapability::ProofCollection),
5368
                    ..Default::default()
5369
                };
5370
                let mut rpc_server = test_rpc_server(
5371
                    network,
5372
                    WalletEntropy::new_pseudorandom(rng.random()),
5373
                    2,
5374
                    cli_args,
5375
                )
5376
                .await;
5377
                let token = cookie_token(&rpc_server).await;
5378

5379
                // --- Init.  get wallet spending key ---
5380
                let genesis_block = Block::genesis(network);
5381
                let wallet_spending_key = rpc_server
5382
                    .state
5383
                    .lock_guard_mut()
5384
                    .await
5385
                    .wallet_state
5386
                    .next_unused_spending_key(KeyType::Generation)
5387
                    .await;
5388

5389
                let SpendingKey::Generation(key) = wallet_spending_key else {
5390
                    // todo: make_mock_block should accept a SpendingKey.
5391
                    panic!("must be generation key");
5392
                };
5393

5394
                // --- Init.  generate a block, with composer fee going to our
5395
                // wallet ---
5396
                let timestamp = network.launch_date() + Timestamp::days(1);
5397
                let (block_1, composer_utxos) =
5398
                    make_mock_block(&genesis_block, Some(timestamp), key, rng.random()).await;
5399

5400
                {
5401
                    let state_lock = rpc_server.state.lock_guard().await;
5402
                    let wallet_status = state_lock.get_wallet_status_for_tip().await;
5403
                    let original_balance = state_lock
5404
                        .wallet_state
5405
                        .confirmed_available_balance(&wallet_status, timestamp);
5406
                    assert!(original_balance.is_zero(), "Original balance assumed zero");
5407
                };
5408

5409
                // --- Init.  append the block to blockchain ---
5410
                rpc_server
5411
                    .state
5412
                    .set_new_self_composed_tip(block_1.clone(), composer_utxos)
5413
                    .await?;
5414

5415
                {
5416
                    let state_lock = rpc_server.state.lock_guard().await;
5417
                    let wallet_status = state_lock.get_wallet_status_for_tip().await;
5418
                    let new_balance = state_lock
5419
                        .wallet_state
5420
                        .confirmed_available_balance(&wallet_status, timestamp);
5421
                    let mut expected_balance = Block::block_subsidy(block_1.header().height);
5422
                    expected_balance.div_two();
5423
                    assert_eq!(
5424
                        expected_balance, new_balance,
5425
                        "New balance must be exactly 1/2 mining reward bc timelock"
5426
                    );
5427
                };
5428

5429
                // --- Setup. generate an output that our wallet cannot claim. ---
5430
                let external_receiving_address: ReceivingAddress = match recipient_key_type {
5431
                    KeyType::Generation => {
5432
                        GenerationReceivingAddress::derive_from_seed(rng.random()).into()
5433
                    }
5434
                    KeyType::Symmetric => SymmetricKey::from_seed(rng.random()).into(),
5435
                };
5436
                let output1: OutputFormat = (
5437
                    external_receiving_address.clone(),
5438
                    NativeCurrencyAmount::coins(5),
5439
                    UtxoNotificationMedium::OffChain,
5440
                )
5441
                    .into();
5442

5443
                // --- Setup. generate an output that our wallet can claim. ---
5444
                let output2: OutputFormat = {
5445
                    let spending_key = rpc_server
5446
                        .state
5447
                        .lock_guard_mut()
5448
                        .await
5449
                        .wallet_state
5450
                        .next_unused_spending_key(recipient_key_type)
5451
                        .await;
5452
                    (
5453
                        spending_key.to_address(),
5454
                        NativeCurrencyAmount::coins(25),
5455
                        UtxoNotificationMedium::OffChain,
5456
                    )
5457
                }
5458
                .into();
5459

5460
                // --- Setup. assemble outputs and fee ---
5461
                let outputs = vec![output1, output2];
5462
                let fee = NativeCurrencyAmount::coins(1);
5463

5464
                // --- Store: store num expected utxo before spend ---
5465
                let num_expected_utxo = rpc_server
5466
                    .state
5467
                    .lock_guard()
5468
                    .await
5469
                    .wallet_state
5470
                    .wallet_db
5471
                    .expected_utxos()
5472
                    .len()
5473
                    .await;
5474

5475
                // --- Operation: perform send_to_many
5476
                // It's important to call a method where you get to inject the
5477
                // timestamp. Otherwise, proofs cannot be reused, and CI will
5478
                // fail. CI might also fail if you don't set an explicit proving
5479
                // capability.
5480
                let result = rpc_server
5481
                    .clone()
5482
                    .send(
5483
                        context::current(),
5484
                        token,
5485
                        outputs,
5486
                        ChangePolicy::recover_to_next_unused_key(
5487
                            KeyType::Symmetric,
5488
                            UtxoNotificationMedium::OffChain,
5489
                        ),
5490
                        fee,
5491
                    )
5492
                    .await;
5493

5494
                // let result = rpc_server
5495
                //     .clone()
5496
                //     .send_to_many_inner(
5497
                //         ctx,
5498
                //         outputs,
5499
                //         (
5500
                //             UtxoNotificationMedium::OffChain,
5501
                //             UtxoNotificationMedium::OffChain,
5502
                //         ),
5503
                //         fee,
5504
                //         timestamp,
5505
                //         TxProvingCapability::ProofCollection,
5506
                //     )
5507
                //     .await;
5508

5509
                // --- Test: bech32m serialize/deserialize roundtrip.
5510
                assert_eq!(
5511
                    external_receiving_address,
5512
                    ReceivingAddress::from_bech32m(
5513
                        &external_receiving_address.to_bech32m(network)?,
5514
                        network,
5515
                    )?
5516
                );
5517

5518
                // --- Test: verify op returns a value.
5519
                assert!(result.is_ok());
5520

5521
                // --- Test: verify expected_utxos.len() has increased by 2.
5522
                //           (one off-chain utxo + one change utxo)
5523
                assert_eq!(
5524
                    rpc_server
5525
                        .state
5526
                        .lock_guard()
5527
                        .await
5528
                        .wallet_state
5529
                        .wallet_db
5530
                        .expected_utxos()
5531
                        .len()
5532
                        .await,
5533
                    num_expected_utxo + 2
5534
                );
5535

5536
                Ok(())
5537
            }
5538
        }
5539
    }
5540
}
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