Skip to content

Commit

Permalink
claim_utxo: verify sender_randomness equality in wallet check
Browse files Browse the repository at this point in the history
When checking if a UTXO/offchain UTXO notification has already been
claimed by this client's wallet, verify that not just the UTXO matches
an entry in the wallet, but that the sender randomness also agrees.

It's quite conceivable that a party will send the same transaction to
an address multiple times, and we want to ensure that the wallet can
catch all such UTXOs.
  • Loading branch information
Sword-Smith committed Nov 20, 2024
1 parent 8592bfd commit 2bacee4
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,8 @@ mod test {
use tasm_lib::twenty_first::bfe;
use test_strategy::proptest;

use crate::config_models::network::Network;

use super::EncryptedUtxoNotification;
use crate::config_models::network::Network;

impl<'a> Arbitrary<'a> for EncryptedUtxoNotification {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
Expand Down
68 changes: 65 additions & 3 deletions src/models/state/wallet/wallet_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -543,12 +543,17 @@ impl WalletState {
.await
}

/// find the `MonitoredUtxo` that matches `utxo`, if any
/// find the `MonitoredUtxo` that matches `utxo` and sender randomness, if
/// any.
///
/// perf: this fn is o(n) with the number of MonitoredUtxo stored. Iteration
/// is performed from newest to oldest based on expectation that we
/// will most often be working with recent MonitoredUtxos.
pub(crate) async fn find_monitored_utxo(&self, utxo: &Utxo) -> Option<MonitoredUtxo> {
pub(crate) async fn find_monitored_utxo(
&self,
utxo: &Utxo,
sender_randomness: Digest,
) -> Option<MonitoredUtxo> {
let len = self.wallet_db.monitored_utxos().len().await;
let stream = self
.wallet_db
Expand All @@ -558,7 +563,13 @@ impl WalletState {
pin_mut!(stream); // needed for iteration

while let Some(mu) = stream.next().await {
if mu.utxo == *utxo {
if mu.utxo == *utxo
&& mu
.get_latest_membership_proof_entry()
.is_some_and(|(_block_digest, msmp)| {
msmp.sender_randomness == sender_randomness
})
{
return Some(mu);
}
}
Expand Down Expand Up @@ -1312,6 +1323,7 @@ impl WalletState {
#[cfg(test)]
mod tests {
use num_traits::One;
use rand::random;
use rand::thread_rng;
use rand::Rng;
use tracing_test::traced_test;
Expand All @@ -1324,6 +1336,56 @@ mod tests {
use crate::tests::shared::mock_genesis_global_state;
use crate::tests::shared::mock_genesis_wallet_state;

#[tokio::test]
#[traced_test]
async fn find_monitored_utxo_test() {
let network = Network::Testnet;
let alice_global_lock = mock_genesis_global_state(
network,
0,
WalletSecret::devnet_wallet(),
cli_args::Args::default(),
)
.await;

let premine_utxo = {
let wallet = &alice_global_lock.lock_guard().await.wallet_state;
Block::premine_utxos(network)
.into_iter()
.find(|premine_utxo| wallet.can_unlock(premine_utxo))
.or_else(|| panic!())
.unwrap()
};
let premine_sender_randomness = Block::premine_sender_randomness(network);

let premine_mutxo = alice_global_lock
.lock_guard()
.await
.wallet_state
.find_monitored_utxo(&premine_utxo, premine_sender_randomness)
.await
.expect("Must be able to find premine MUTXO with this method");
assert_eq!(premine_utxo, premine_mutxo.utxo);

let genesis_digest = Block::genesis_block(network).hash();
assert_eq!(
premine_sender_randomness,
premine_mutxo
.get_membership_proof_for_block(genesis_digest)
.unwrap()
.sender_randomness
);

// Using another sender randomness returns nothing
assert!(alice_global_lock
.lock_guard()
.await
.wallet_state
.find_monitored_utxo(&premine_utxo, random())
.await
.is_none());
}

#[tokio::test]
#[traced_test]
async fn does_not_make_tx_with_timelocked_utxos() {
Expand Down
2 changes: 1 addition & 1 deletion src/rpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,7 @@ impl NeptuneRPCServer {
// search for matching monitored utxo and return early if found.
if state
.wallet_state
.find_monitored_utxo(&utxo_notification.utxo)
.find_monitored_utxo(&utxo_notification.utxo, utxo_notification.sender_randomness)
.await
.is_some()
{
Expand Down

0 comments on commit 2bacee4

Please sign in to comment.