Skip to content

Commit 17116ae

Browse files
committed
bench(wallet): Add benchmark of wallet state update
Add a benchmark that measures the time it takes to update a wallet monitoring 100 UTXOs with a block in which an additional 100 UTXOs are received. This benchmark is added because power users report slow state-updating times, and the *very* slow (up to 5 seconds) state updates have been pinpointed to being in the state-updater by block for the wallet.
1 parent 825a3b2 commit 17116ae

14 files changed

+235
-18
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,7 @@ required-features = ["arbitrary-impls"]
296296
[[bench]]
297297
name = "fast_kernel_mast_hash"
298298
harness = false
299+
300+
[[bench]]
301+
name = "wallet_state"
302+
harness = false

benches/wallet_state.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use divan::Bencher;
2+
use neptune_cash::api::export::KeyType;
3+
use neptune_cash::api::export::NativeCurrencyAmount;
4+
use neptune_cash::api::export::NeptuneProof;
5+
use neptune_cash::api::export::Network;
6+
use neptune_cash::api::export::ReceivingAddress;
7+
use neptune_cash::api::export::Timestamp;
8+
use neptune_cash::api::export::Transaction;
9+
use neptune_cash::api::export::TransactionDetails;
10+
use neptune_cash::api::export::TransactionProof;
11+
use neptune_cash::api::export::TxInput;
12+
use neptune_cash::models::blockchain::block::validity::block_primitive_witness::BlockPrimitiveWitness;
13+
use neptune_cash::models::blockchain::block::validity::block_proof_witness::BlockProofWitness;
14+
use neptune_cash::models::blockchain::block::Block;
15+
use neptune_cash::models::blockchain::block::BlockProof;
16+
use neptune_cash::models::blockchain::transaction::primitive_witness::PrimitiveWitness;
17+
use neptune_cash::models::state::wallet::transaction_output::TxOutput;
18+
use neptune_cash::models::state::wallet::wallet_state::WalletState;
19+
use num_traits::CheckedSub;
20+
use rand::Rng;
21+
22+
fn main() {
23+
divan::main();
24+
}
25+
26+
mod maintain_membership_proofs {
27+
use super::*;
28+
29+
/// Maintain 100 membership proofs, while receiving an additional 100 UTXOs.
30+
mod maintain_100_100 {
31+
32+
use neptune_cash::api::benchmarks::devops_wallet_state_genesis;
33+
34+
use super::*;
35+
use crate::helper::next_block_incoming_utxos;
36+
37+
fn update_wallet_with_block2(bencher: Bencher) {
38+
let rt = tokio::runtime::Runtime::new().unwrap();
39+
let mut wallet_state = rt.block_on(devops_wallet_state_genesis(Network::Main));
40+
41+
let genesis = Block::genesis(Network::Main);
42+
let own_address = rt
43+
.block_on(wallet_state.next_unused_spending_key(KeyType::Generation))
44+
.to_address();
45+
let block1_time = Network::Main.launch_date() + Timestamp::months(7);
46+
let block1 = rt.block_on(next_block_incoming_utxos(
47+
&genesis,
48+
own_address.clone(),
49+
100,
50+
&wallet_state,
51+
block1_time,
52+
));
53+
54+
rt.block_on(async {
55+
wallet_state
56+
.update_wallet_state_with_new_block(
57+
&genesis.mutator_set_accumulator_after(),
58+
&block1,
59+
)
60+
.await
61+
.unwrap()
62+
});
63+
64+
let block2_time = block1_time + Timestamp::hours(1);
65+
let block2 = rt.block_on(next_block_incoming_utxos(
66+
&block1,
67+
own_address,
68+
100,
69+
&wallet_state,
70+
block2_time,
71+
));
72+
73+
// Benchmark the receival of 100 UTXOs while already managing 100
74+
// UTXOs in the wallet.
75+
bencher.bench_local(|| {
76+
rt.block_on(async {
77+
wallet_state
78+
.update_wallet_state_with_new_block(
79+
&block1.mutator_set_accumulator_after(),
80+
&block2,
81+
)
82+
.await
83+
.unwrap()
84+
});
85+
});
86+
}
87+
88+
#[divan::bench(sample_count = 10)]
89+
fn apply_block2(bencher: Bencher) {
90+
update_wallet_with_block2(bencher);
91+
}
92+
}
93+
}
94+
95+
mod helper {
96+
use super::*;
97+
98+
/// Sends the wallet's entire balance to the provided address. Divides the
99+
/// transaction up into `N` outputs, guaranteeing that the entire available
100+
/// balance is being spent.
101+
pub async fn next_block_incoming_utxos(
102+
parent: &Block,
103+
recipient: ReceivingAddress,
104+
num_outputs: usize,
105+
sender: &WalletState,
106+
timestamp: Timestamp,
107+
) -> Block {
108+
let one_nau = NativeCurrencyAmount::from_nau(1);
109+
110+
let fee = one_nau;
111+
112+
// create N outputs of 1 nau each
113+
114+
let mut outputs = vec![(recipient.clone(), one_nau); num_outputs - 1];
115+
116+
// calc remaining amount and add it to outputs
117+
let intermediate_spend = outputs
118+
.iter()
119+
.map(|(_, amt)| *amt)
120+
.sum::<NativeCurrencyAmount>()
121+
+ fee;
122+
123+
let msa = parent.mutator_set_accumulator_after();
124+
let wallet_status = sender.get_wallet_status(parent.hash(), &msa).await;
125+
let change_amt = wallet_status
126+
.synced_unspent_available_amount(timestamp)
127+
.checked_sub(&intermediate_spend)
128+
.unwrap();
129+
130+
outputs.push((recipient.clone(), change_amt));
131+
132+
let mut input_funds: Vec<TxInput> = vec![];
133+
for input in sender.spendable_inputs(wallet_status, timestamp) {
134+
input_funds.push(input);
135+
}
136+
137+
let mut rng = rand::rng();
138+
let outputs = outputs.into_iter().map(|(recipient, amount)| {
139+
TxOutput::onchain_native_currency_as_change(amount, rng.random(), recipient)
140+
});
141+
let tx_details = TransactionDetails::new_without_coinbase(
142+
input_funds,
143+
outputs,
144+
fee,
145+
timestamp,
146+
msa,
147+
Network::Main,
148+
);
149+
150+
let kernel = PrimitiveWitness::from_transaction_details(&tx_details).kernel;
151+
152+
let tx = Transaction {
153+
kernel,
154+
proof: TransactionProof::SingleProof(NeptuneProof::invalid()),
155+
};
156+
157+
let block_primitive_witness = BlockPrimitiveWitness::new(parent.to_owned(), tx);
158+
let body = block_primitive_witness.body().to_owned();
159+
let header = block_primitive_witness.header(timestamp, None);
160+
let (appendix, proof) = {
161+
let block_proof_witness = BlockProofWitness::produce(block_primitive_witness);
162+
let appendix = block_proof_witness.appendix();
163+
(appendix, BlockProof::SingleProof(NeptuneProof::invalid()))
164+
};
165+
166+
Block::new(header, body, appendix, proof)
167+
}
168+
}

src/api/benchmarks.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use std::env;
2+
use std::path::Path;
3+
use std::path::PathBuf;
4+
5+
use anyhow::Result;
6+
use rand::distr::Alphanumeric;
7+
use rand::distr::SampleString;
8+
9+
use crate::api::export::Network;
10+
use crate::config_models::cli_args;
11+
use crate::config_models::data_directory::DataDirectory;
12+
use crate::models::state::wallet::wallet_configuration::WalletConfiguration;
13+
use crate::models::state::wallet::wallet_entropy::WalletEntropy;
14+
use crate::models::state::wallet::wallet_state::WalletState;
15+
16+
fn benchmark_data_directory(network: Network) -> Result<DataDirectory> {
17+
let mut rng = rand::rng();
18+
let user = env::var("USER").unwrap_or_else(|_| "default".to_string());
19+
let tmp_root: PathBuf = env::temp_dir()
20+
.join(format!("neptune-benchmark-runs-{}", user))
21+
.join(Path::new(&Alphanumeric.sample_string(&mut rng, 16)));
22+
23+
DataDirectory::get(Some(tmp_root), network)
24+
}
25+
26+
pub async fn devops_wallet_state_genesis(network: Network) -> WalletState {
27+
let data_directory = benchmark_data_directory(network).unwrap();
28+
DataDirectory::create_dir_if_not_exists(&data_directory.root_dir_path())
29+
.await
30+
.unwrap();
31+
32+
DataDirectory::create_dir_if_not_exists(&data_directory.wallet_directory_path())
33+
.await
34+
.unwrap();
35+
let cli_args = cli_args::Args::default();
36+
let configuration = WalletConfiguration::new(&data_directory).absorb_options(&cli_args);
37+
38+
WalletState::try_new(configuration, WalletEntropy::devnet_wallet())
39+
.await
40+
.unwrap()
41+
}

src/api/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
//! Please read the [GlobalStateLock](crate::GlobalStateLock) docs carefully because it is critical
4646
//! not to hold the lock too long or cause a deadlock situation.
4747
mod api_impl;
48+
pub mod benchmarks;
4849
pub mod export;
4950
pub mod regtest;
5051
pub mod tx_initiation;

src/config_models/network.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ pub enum Network {
4040
}
4141

4242
impl Network {
43-
pub(crate) fn launch_date(&self) -> Timestamp {
43+
/// Launch date of the network in question. Defined as the timestamp of the
44+
/// genesis block.
45+
pub fn launch_date(&self) -> Timestamp {
4446
match self {
4547
Network::RegTest => {
4648
const SEVEN_DAYS: u64 = 1000 * 60 * 60 * 24 * 7;

src/models/blockchain/block/block_appendix.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub(crate) const MAX_NUM_CLAIMS: usize = 500;
2727
/// The appendix can softly be extended with new claims.
2828
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, BFieldCodec, GetSize, Default)]
2929
#[cfg_attr(any(test, feature = "arbitrary-impls"), derive(Arbitrary))]
30-
pub(crate) struct BlockAppendix {
30+
pub struct BlockAppendix {
3131
claims: Vec<Claim>,
3232
}
3333

src/models/blockchain/block/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ impl Block {
379379
///
380380
/// Includes the guesser-fee UTXOs which are not included by the
381381
/// `mutator_set_accumulator` field on the block body.
382-
pub(crate) fn mutator_set_accumulator_after(&self) -> MutatorSetAccumulator {
382+
pub fn mutator_set_accumulator_after(&self) -> MutatorSetAccumulator {
383383
let mut msa = self.kernel.body.mutator_set_accumulator.clone();
384384
let mutator_set_update = MutatorSetUpdate::new(vec![], self.guesser_fee_addition_records());
385385
mutator_set_update.apply_to_accumulator(&mut msa)
@@ -621,7 +621,7 @@ impl Block {
621621
utxos
622622
}
623623

624-
pub(crate) fn new(
624+
pub fn new(
625625
header: BlockHeader,
626626
body: BlockBody,
627627
appendix: BlockAppendix,

src/models/blockchain/block/validity/block_primitive_witness.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ use crate::models::proof_abstractions::timestamp::Timestamp;
3939
// note: I removed unused PartialEq, Eq derive. If we ever need one
4040
// PartialEq should be derived manually to ignore maybe_body.
4141
#[derive(Clone, Debug)]
42-
pub(crate) struct BlockPrimitiveWitness {
42+
pub struct BlockPrimitiveWitness {
4343
predecessor_block: Block,
4444
transaction: Transaction,
4545

4646
maybe_body: OnceLock<BlockBody>,
4747
}
4848

4949
impl BlockPrimitiveWitness {
50-
pub(crate) fn new(predecessor_block: Block, transaction: Transaction) -> Self {
50+
pub fn new(predecessor_block: Block, transaction: Transaction) -> Self {
5151
Self {
5252
predecessor_block,
5353
transaction,
@@ -59,7 +59,7 @@ impl BlockPrimitiveWitness {
5959
&self.transaction
6060
}
6161

62-
pub(crate) fn header(
62+
pub fn header(
6363
&self,
6464
timestamp: Timestamp,
6565
target_block_interval: Option<Timestamp>,
@@ -74,7 +74,7 @@ impl BlockPrimitiveWitness {
7474
)
7575
}
7676

77-
pub(crate) fn body(&self) -> &BlockBody {
77+
pub fn body(&self) -> &BlockBody {
7878
self.maybe_body.get_or_init(|| {
7979
let predecessor_msa_digest = self.predecessor_block
8080
.mutator_set_accumulator_after()

src/models/blockchain/block/validity/block_proof_witness.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::models::proof_abstractions::SecretWitness;
3131
///
3232
/// This is the witness for the [`BlockProgram`].
3333
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec, TasmObject)]
34-
pub(crate) struct BlockProofWitness {
34+
pub struct BlockProofWitness {
3535
pub(super) block_body: BlockBody,
3636
pub(crate) claims: Vec<Claim>,
3737
pub(crate) proofs: Vec<Proof>,
@@ -63,11 +63,11 @@ impl BlockProofWitness {
6363
self.claims.clone()
6464
}
6565

66-
pub(crate) fn appendix(&self) -> BlockAppendix {
66+
pub fn appendix(&self) -> BlockAppendix {
6767
BlockAppendix::new(self.claims())
6868
}
6969

70-
pub(crate) fn produce(block_primitive_witness: BlockPrimitiveWitness) -> BlockProofWitness {
70+
pub fn produce(block_primitive_witness: BlockPrimitiveWitness) -> BlockProofWitness {
7171
let txk_mast_hash = block_primitive_witness
7272
.body()
7373
.transaction_kernel

src/models/blockchain/transaction/primitive_witness.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ impl PrimitiveWitness {
241241
}
242242

243243
/// Create a [`PrimitiveWitness`] from [`TransactionDetails`].
244-
pub(crate) fn from_transaction_details(transaction_details: &TransactionDetails) -> Self {
244+
pub fn from_transaction_details(transaction_details: &TransactionDetails) -> Self {
245245
let TransactionDetails {
246246
tx_inputs,
247247
tx_outputs,

src/models/state/transaction_details.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ impl TransactionDetails {
215215
/// - mutator set membership proofs, must be valid wrt. supplied mutator set
216216
///
217217
/// See also: [Self::new_with_coinbase].
218-
pub(crate) fn new_without_coinbase(
218+
pub fn new_without_coinbase(
219219
tx_inputs: impl Into<TxInputList>,
220220
tx_outputs: impl Into<TxOutputList>,
221221
fee: NativeCurrencyAmount,

src/models/state/wallet/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub(crate) mod wallet_configuration;
1717
pub(crate) mod wallet_db_tables;
1818
pub(crate) mod wallet_entropy;
1919
pub mod wallet_file;
20-
pub(crate) mod wallet_state;
20+
pub mod wallet_state;
2121
pub mod wallet_status;
2222

2323
#[cfg(test)]

src/models/state/wallet/transaction_output.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ impl TxOutput {
292292

293293
/// Instantiate a [TxOutput] for native currency intended fro on-chain UTXO
294294
/// notification.
295-
pub(crate) fn onchain_native_currency(
295+
pub fn onchain_native_currency(
296296
amount: NativeCurrencyAmount,
297297
sender_randomness: Digest,
298298
receiving_address: ReceivingAddress,
@@ -311,7 +311,7 @@ impl TxOutput {
311311

312312
/// Instantiate a [TxOutput] for native currency intended fro on-chain UTXO
313313
/// notification.
314-
pub(crate) fn onchain_native_currency_as_change(
314+
pub fn onchain_native_currency_as_change(
315315
amount: NativeCurrencyAmount,
316316
sender_randomness: Digest,
317317
receiving_address: ReceivingAddress,

src/models/state/wallet/wallet_state.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,7 +1345,7 @@ impl WalletState {
13451345
///
13461346
/// Assume the given block is valid and that the wallet state is not synced
13471347
/// with the new block yet but is synced with the previous block (if any).
1348-
pub(crate) async fn update_wallet_state_with_new_block(
1348+
pub async fn update_wallet_state_with_new_block(
13491349
&mut self,
13501350
previous_mutator_set_accumulator: &MutatorSetAccumulator,
13511351
new_block: &Block,
@@ -1424,6 +1424,7 @@ impl WalletState {
14241424
all_existing_mutxos,
14251425
)
14261426
}
1427+
14271428
let tx_kernel = new_block.kernel.body.transaction_kernel.clone();
14281429

14291430
let spent_inputs: Vec<(Utxo, AbsoluteIndexSet, u64)> =
@@ -1837,7 +1838,7 @@ impl WalletState {
18371838
/// + that are timelocked in the future
18381839
/// + that are unspendable (no spending key)
18391840
/// + that are already spent in the mempool
1840-
pub(crate) fn spendable_inputs(
1841+
pub fn spendable_inputs(
18411842
&self,
18421843
wallet_status: WalletStatus,
18431844
timestamp: Timestamp,

0 commit comments

Comments
 (0)