diff --git a/src/main_loop.rs b/src/main_loop.rs index 8d746c0a..1108f6fa 100644 --- a/src/main_loop.rs +++ b/src/main_loop.rs @@ -37,7 +37,6 @@ use crate::models::peer::HandshakeData; use crate::models::peer::PeerInfo; use crate::models::peer::PeerSynchronizationState; use crate::models::peer::TransactionNotification; -use crate::models::state::wallet::expected_utxo; use crate::models::state::GlobalStateLock; use crate::prelude::twenty_first; diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 86593005..ad4834fe 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -16,32 +16,29 @@ use tokio::sync::mpsc; use tokio::sync::watch; use tokio::task::JoinHandle; use tracing::*; +use transaction_output::TxOutput; use twenty_first::amount::u32s::U32s; use twenty_first::math::b_field_element::BFieldElement; use twenty_first::math::digest::Digest; -use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use crate::models::blockchain::block::block_body::BlockBody; use crate::models::blockchain::block::block_header::BlockHeader; use crate::models::blockchain::block::block_height::BlockHeight; use crate::models::blockchain::block::mutator_set_update::*; use crate::models::blockchain::block::*; -use crate::models::blockchain::shared::*; -use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; -use crate::models::blockchain::transaction::utxo::*; -use crate::models::blockchain::transaction::validity::single_proof::SingleProof; use crate::models::blockchain::transaction::*; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::channel::*; use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::shared::SIZE_20MB_IN_BYTES; +use crate::models::state::transaction_details::TransactionDetails; +use crate::models::state::tx_proving_capability::TxProvingCapability; use crate::models::state::wallet::expected_utxo::ExpectedUtxo; use crate::models::state::wallet::expected_utxo::UtxoNotifier; use crate::models::state::wallet::WalletSecret; use crate::models::state::GlobalState; use crate::models::state::GlobalStateLock; use crate::prelude::twenty_first; -use crate::util_types::mutator_set::commit; use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; const MOCK_MAX_BLOCK_SIZE: u32 = 1_000_000; @@ -243,11 +240,11 @@ Difficulty threshold: {threshold} .unwrap_or_else(|_| warn!("Receiver in mining loop closed prematurely")) } -pub(crate) fn make_coinbase_transaction( +pub(crate) async fn make_coinbase_transaction( global_state: &GlobalState, transaction_fees: NeptuneCoins, timestamp: Timestamp, -) -> (Transaction, ExpectedUtxo) { +) -> Result<(Transaction, ExpectedUtxo)> { // note: it is Ok to always use the same key here because: // 1. if we find a block, the utxo will go to our wallet // and notification occurs offchain, so there is no privacy issue. @@ -260,76 +257,66 @@ pub(crate) fn make_coinbase_transaction( .wallet_secret .nth_generation_spending_key(0); let receiving_address = coinbase_recipient_spending_key.to_address(); - let receiver_digest = receiving_address.privacy_digest; let latest_block = global_state.chain.light_state(); let mutator_set_accumulator = latest_block.body().mutator_set_accumulator.clone(); let next_block_height: BlockHeight = latest_block.header().height.next(); - let lock_script = receiving_address.lock_script(); let coinbase_amount = Block::get_mining_reward(next_block_height) + transaction_fees; - let coinbase_utxo = Utxo::new_native_currency(lock_script, coinbase_amount); let sender_randomness: Digest = global_state .wallet_state .wallet_secret - .generate_sender_randomness(next_block_height, receiver_digest); + .generate_sender_randomness(next_block_height, receiving_address.privacy_digest); - let coinbase_addition_record = commit( - Hash::hash(&coinbase_utxo), + // There is no reason to put coinbase UTXO notifications on chain, because: + // Both sender randomness and receiver preimage are derived + // deterministically from the wallet's seed. + let coinbase_output = TxOutput::offchain_native_currency( + coinbase_amount, sender_randomness, - receiver_digest, + receiving_address.into(), ); - let kernel = TransactionKernel { - inputs: vec![], - outputs: vec![coinbase_addition_record], - public_announcements: vec![], - fee: NeptuneCoins::zero(), - coinbase: Some(coinbase_amount), - timestamp, - mutator_set_hash: mutator_set_accumulator.hash(), - }; - - let primitive_witness = GlobalState::generate_primitive_witness( + let transaction_details = TransactionDetails::new_with_coinbase( vec![], - vec![coinbase_utxo.clone()], - vec![sender_randomness], - vec![receiver_digest], - kernel, + vec![coinbase_output.clone()].into(), + coinbase_amount, + timestamp, mutator_set_accumulator, + ) + .expect( + "all inputs' ms membership proofs must be valid because inputs are empty;\ + and tx must be balanced because the one output receives exactly the coinbase amount", ); - let utxo_info_for_coinbase = ExpectedUtxo::new( - coinbase_utxo, - sender_randomness, - coinbase_recipient_spending_key.privacy_preimage, - UtxoNotifier::OwnMiner, - ); - + // 2. Create the transaction // A coinbase transaction implies mining. So you *must* // be able to create a SingleProof. + info!("Start: generate single proof for coinbase transaction"); - let proof = SingleProof::produce(&primitive_witness); + let transaction = + GlobalState::create_raw_transaction(transaction_details, TxProvingCapability::SingleProof) + .await?; info!("Done: generating single proof for coinbase transaction"); - ( - Transaction { - kernel: primitive_witness.kernel, - proof: TransactionProof::SingleProof(proof), - }, - utxo_info_for_coinbase, - ) + let utxo_info_for_coinbase = ExpectedUtxo::new( + coinbase_output.utxo(), + coinbase_output.sender_randomness(), + coinbase_recipient_spending_key.privacy_preimage, + UtxoNotifier::OwnMiner, + ); + + Ok((transaction, utxo_info_for_coinbase)) } /// Create the transaction that goes into the block template. The transaction is /// built from the mempool and from the coinbase transaction. Also returns the /// "sender randomness" used in the coinbase transaction. -fn create_block_transaction( +async fn create_block_transaction( predecessor_block: &Block, global_state: &GlobalState, timestamp: Timestamp, -) -> (Transaction, ExpectedUtxo) { - /// Return the seed that is used when shuffling inputs and outputs in the - /// transaction merger. +) -> Result<(Transaction, ExpectedUtxo)> { + /// Return the a seed used to randomize shuffling. fn shuffle_seed(wallet_secret: &WalletSecret, block_height: BlockHeight) -> [u8; 32] { let secure_seed_from_wallet = wallet_secret.deterministic_derived_seed(block_height); let seed: [u8; Digest::BYTES] = secure_seed_from_wallet.into(); @@ -348,25 +335,35 @@ fn create_block_transaction( let transaction_fees = transactions_to_include .iter() .fold(NeptuneCoins::zero(), |acc, tx| acc + tx.kernel.fee); - let next_block_height: BlockHeight = predecessor_block.kernel.header.height.next(); let (coinbase_transaction, coinbase_as_expected_utxo) = - make_coinbase_transaction(global_state, transaction_fees, timestamp); + make_coinbase_transaction(global_state, transaction_fees, timestamp).await?; debug!( "Creating block transaction with mutator set hash: {}", predecessor_block.kernel.body.mutator_set_accumulator.hash() ); + let mut rng: StdRng = SeedableRng::from_seed(shuffle_seed( + &global_state.wallet_state.wallet_secret, + predecessor_block.kernel.header.height.next(), + )); + // Merge incoming transactions with the coinbase transaction - let shuffle_seed = shuffle_seed(&global_state.wallet_state.wallet_secret, next_block_height); - let merged_transaction = transactions_to_include - .into_iter() - .fold(coinbase_transaction, |acc, transaction| { - Transaction::merge_with(acc, transaction, shuffle_seed) - }); - - (merged_transaction, coinbase_as_expected_utxo) + let num_transactions_to_include = transactions_to_include.len(); + let merged_transaction = transactions_to_include.into_iter().enumerate().fold( + coinbase_transaction, + |acc, (i, transaction)| { + info!( + "Merging transaction {} / {}", + i + 1, + num_transactions_to_include + ); + Transaction::merge_with(acc, transaction, rng.gen()) + }, + ); + + Ok((merged_transaction, coinbase_as_expected_utxo)) } /// Locking: @@ -402,7 +399,8 @@ pub async fn mine( &latest_block, global_state_lock.lock_guard().await.deref(), now, - ); + ) + .await?; let (block_header, block_body, block_proof) = make_block_template(&latest_block, transaction, now, None); let miner_task = mine_block( @@ -572,10 +570,13 @@ mod mine_loop_tests { ); // Verify constructed coinbase transaction and block template when mempool is empty - let alice_mut = alice.lock_guard().await; - let (transaction_empty_mempool, _coinbase_utxo_info) = - make_coinbase_transaction(&alice_mut, NeptuneCoins::zero(), now); - drop(alice_mut); + let (transaction_empty_mempool, _coinbase_utxo_info) = { + let alice = alice.lock_guard().await; + + make_coinbase_transaction(&alice, NeptuneCoins::zero(), now) + .await + .unwrap() + }; assert_eq!( 1, @@ -604,16 +605,6 @@ mod mine_loop_tests { ); // Add a transaction to the mempool - // let four_neptune_coins = NeptuneCoins::new(4).to_native_coins(); - // let receiver_privacy_digest = Digest::default(); - // let sender_randomness = Digest::default(); - // let tx_output = Utxo { - // coins: four_neptune_coins, - // lock_script_hash: LockScript::anyone_can_spend().hash(), - // }; - // let tx_output = TxOutput::offchain_native_currency(NeptuneCoins::new(4), Digest::default(), receiving_address) - // let utxo = Utxo::new(LockScript::anyone_can_spend(), NeptuneCoins::new(4)); - // let tx_output = TxOutput::offchain(utxo, Digest::default(), ) let alice_key = alice .lock_guard() .await @@ -645,10 +636,12 @@ mod mine_loop_tests { assert_eq!(1, alice.lock_guard().await.mempool.len()); // Build transaction for block - let alice_mut = alice.lock_guard().await; - let (transaction_non_empty_mempool, _new_coinbase_sender_randomness) = - create_block_transaction(&genesis_block, &alice_mut, in_seven_months); - drop(alice_mut); + let (transaction_non_empty_mempool, _new_coinbase_sender_randomness) = { + let alice = alice.lock_guard().await; + create_block_transaction(&genesis_block, &alice, in_seven_months) + .await + .unwrap() + }; assert_eq!( 3, transaction_non_empty_mempool.kernel.outputs.len(), @@ -704,7 +697,9 @@ mod mine_loop_tests { let launch_date = tip_block_orig.header().timestamp; let (transaction, coinbase_utxo_info) = - make_coinbase_transaction(&global_state, NeptuneCoins::zero(), launch_date); + make_coinbase_transaction(&global_state, NeptuneCoins::zero(), launch_date) + .await + .unwrap(); let (block_header, block_body, block_proof) = Block::make_block_template(tip_block_orig, transaction, launch_date, None); @@ -766,6 +761,8 @@ mod mine_loop_tests { let (transaction, coinbase_utxo_info) = { let global_state = global_state_lock.lock_guard().await; make_coinbase_transaction(&global_state, NeptuneCoins::zero(), ten_seconds_ago) + .await + .unwrap() }; let (block_header, block_body, block_proof) = diff --git a/src/models/blockchain/block/mod.rs b/src/models/blockchain/block/mod.rs index cfff0b34..40323b3b 100644 --- a/src/models/blockchain/block/mod.rs +++ b/src/models/blockchain/block/mod.rs @@ -769,7 +769,6 @@ impl Block { mod block_tests { use std::collections::HashSet; - use rand::random; use rand::thread_rng; use rand::Rng; use strum::IntoEnumIterator; diff --git a/src/models/blockchain/transaction/transaction_output.rs b/src/models/blockchain/transaction/transaction_output.rs index 39a4aa97..69caba0b 100644 --- a/src/models/blockchain/transaction/transaction_output.rs +++ b/src/models/blockchain/transaction/transaction_output.rs @@ -58,6 +58,8 @@ pub(crate) struct UtxoNotificationPayload { } impl UtxoNotificationPayload { + // TODO: Remove test flag when used in main code. + #[cfg(test)] pub(crate) fn new(utxo: Utxo, sender_randomness: Digest) -> Self { Self { utxo, @@ -174,6 +176,7 @@ impl TxOutput { /// /// Warning: If care is not taken, this is an easy way to lose funds. /// Don't use this constructor unless you have a good reason to. + #[cfg(test)] pub(crate) fn no_notification( utxo: Utxo, sender_randomness: Digest, @@ -262,6 +265,16 @@ impl Deref for TxOutputList { } } +impl IntoIterator for TxOutputList { + type Item = TxOutput; + + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + impl DerefMut for TxOutputList { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 @@ -380,10 +393,6 @@ impl TxOutputList { self.0.push(tx_output); } - pub(crate) fn to_vec(&self) -> Vec { - self.0.clone() - } - pub(crate) fn concat_with(mut self, maybe_tx_output: T) -> Self where T: IntoIterator, diff --git a/src/models/state/archival_state.rs b/src/models/state/archival_state.rs index 9f397b77..8069fce2 100644 --- a/src/models/state/archival_state.rs +++ b/src/models/state/archival_state.rs @@ -861,7 +861,6 @@ mod archival_state_tests { use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::state::archival_state::ArchivalState; use crate::models::state::tx_proving_capability::TxProvingCapability; - use crate::models::state::wallet::expected_utxo::ExpectedUtxo; use crate::models::state::wallet::expected_utxo::UtxoNotifier; use crate::models::state::wallet::WalletSecret; use crate::tests::shared::add_block_to_archival_state; @@ -1278,8 +1277,20 @@ mod archival_state_tests { .nth_generation_spending_key(0); let mut genesis = mock_genesis_global_state(network, 3, genesis_wallet_state.wallet_secret).await; - let mut rng = StdRng::seed_from_u64(41251549301u64); + assert_eq!( + 1, + genesis + .lock_guard() + .await + .wallet_state + .wallet_db + .monitored_utxos() + .len() + .await, + "Genesis receiver must have non-empty list of monitored UTXOs" + ); + let mut rng = StdRng::seed_from_u64(41251549301u64); let wallet_secret_alice = WalletSecret::new_pseudorandom(rng.gen()); let alice_spending_key = wallet_secret_alice.nth_generation_spending_key(0); let mut alice = mock_genesis_global_state(network, 3, wallet_secret_alice).await; @@ -1357,6 +1368,8 @@ mod archival_state_tests { let (cbtx, cb_expected) = { let genesis_state = genesis.lock_guard().await; make_coinbase_transaction(&genesis_state, NeptuneCoins::zero(), in_seven_months) + .await + .unwrap() }; let block_tx = cbtx.merge_with(tx_to_alice_and_bob, Default::default()); @@ -1384,8 +1397,12 @@ mod archival_state_tests { .extract_expected_utxos(vec![change_utxo.unwrap()].into(), UtxoNotifier::Cli); genesis_state .wallet_state - .add_expected_utxos(expected_utxos); - genesis_state.wallet_state.add_expected_utxo(cb_expected); + .add_expected_utxos(expected_utxos) + .await; + genesis_state + .wallet_state + .add_expected_utxo(cb_expected) + .await; } // UTXOs for this transaction are communicated offline. So must be @@ -1395,7 +1412,10 @@ mod archival_state_tests { let expected_utxos = alice_state .wallet_state .extract_expected_utxos(receiver_data_for_alice.into(), UtxoNotifier::Cli); - alice_state.wallet_state.add_expected_utxos(expected_utxos); + alice_state + .wallet_state + .add_expected_utxos(expected_utxos) + .await; } { @@ -1403,7 +1423,10 @@ mod archival_state_tests { let expected_utxos = bob_state .wallet_state .extract_expected_utxos(receiver_data_for_bob.into(), UtxoNotifier::Cli); - bob_state.wallet_state.add_expected_utxos(expected_utxos); + bob_state + .wallet_state + .add_expected_utxos(expected_utxos) + .await; } // Update chain states @@ -1548,6 +1571,8 @@ mod archival_state_tests { let (cbtx2, cb_expected2) = { let genesis_state = genesis.lock_guard().await; make_coinbase_transaction(&genesis_state, NeptuneCoins::zero(), in_seven_months) + .await + .unwrap() }; let block_tx2 = cbtx2 .merge_with(tx_from_alice, Default::default()) @@ -1565,14 +1590,10 @@ mod archival_state_tests { { let mut genesis = genesis.lock_guard_mut().await; let expected = genesis.wallet_state.extract_expected_utxos( - outputs_from_bob.concat_with(outputs_from_alice.to_vec()), + outputs_from_bob.concat_with(outputs_from_alice), UtxoNotifier::Cli, ); - genesis - .wallet_state - .add_expected_utxos(expected) - .await - .unwrap(); + genesis.wallet_state.add_expected_utxos(expected).await; } genesis diff --git a/src/models/state/mempool.rs b/src/models/state/mempool.rs index 5ad89507..301f03f2 100644 --- a/src/models/state/mempool.rs +++ b/src/models/state/mempool.rs @@ -470,7 +470,6 @@ mod tests { use crate::models::blockchain::transaction::transaction_output::TxOutput; use crate::models::blockchain::transaction::transaction_output::TxOutputList; use crate::models::blockchain::transaction::transaction_output::UtxoNotificationMedium; - use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::transaction::Transaction; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::shared::SIZE_20MB_IN_BYTES; @@ -666,10 +665,7 @@ mod tests { utxos_from_bob.concat_with(maybe_change_output), UtxoNotifier::Myself, ); - bob.wallet_state - .add_expected_utxos(expected_utxos) - .await - .unwrap(); + bob.wallet_state.add_expected_utxos(expected_utxos).await; // Add this transaction to a mempool let mut mempool = Mempool::new(ByteSize::gb(1), block_1.hash()); @@ -699,7 +695,9 @@ mod tests { // Create next block which includes preminer's transaction let (coinbase_transaction, _expected_utxo) = - make_coinbase_transaction(&bob, NeptuneCoins::zero(), in_eight_months); + make_coinbase_transaction(&bob, NeptuneCoins::zero(), in_eight_months) + .await + .unwrap(); let block_transaction = tx_by_bob.merge_with(coinbase_transaction, Default::default()); let block_2 = Block::new_block_from_template(&block_1, block_transaction, in_eight_months, None); @@ -726,7 +724,9 @@ mod tests { alice.set_new_tip(block_2.clone()).await.unwrap(); bob.set_new_tip(block_2.clone()).await.unwrap(); let (coinbase_transaction2, _expected_utxo2) = - make_coinbase_transaction(&bob, NeptuneCoins::zero(), in_eight_months); + make_coinbase_transaction(&bob, NeptuneCoins::zero(), in_eight_months) + .await + .unwrap(); let block_transaction2 = tx_by_alice_updated .clone() .merge_with(coinbase_transaction2, Default::default()); @@ -764,7 +764,9 @@ mod tests { tx_by_alice_updated = mempool.get_transactions_for_block(usize::MAX)[0].clone(); let (coinbase_transaction3, _expected_utxo3) = - make_coinbase_transaction(&alice, NeptuneCoins::zero(), in_eight_months); + make_coinbase_transaction(&alice, NeptuneCoins::zero(), in_eight_months) + .await + .unwrap(); let block_transaction3 = coinbase_transaction3.merge_with(tx_by_alice_updated, Default::default()); let block_7 = Block::new_block_from_template( @@ -810,7 +812,6 @@ mod tests { .wallet_state .wallet_secret .nth_generation_spending_key_for_tests(0); - let alice_address = alice_key.to_address(); let mut rng: StdRng = StdRng::seed_from_u64(u64::from_str_radix("42", 6).unwrap()); let bob_wallet_secret = WalletSecret::new_pseudorandom(rng.gen()); diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index eb10a1bb..4be35b0e 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -63,7 +63,6 @@ use crate::models::state::wallet::monitored_utxo::MonitoredUtxo; use crate::prelude::twenty_first; use crate::time_fn_call_async; use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; -use crate::util_types::mutator_set::removal_record::RemovalRecord; use crate::Hash; use crate::VERSION; @@ -397,21 +396,6 @@ impl GlobalState { history } - /// Given a list of spendable UTXOs, generate the corresponding removal - /// recods relative to the current mutator set accumulator. - pub(crate) fn generate_removal_records( - utxo_unlockers: &[UnlockedUtxo], - mutator_set_accumulator: &MutatorSetAccumulator, - ) -> Vec { - let mut inputs: Vec = vec![]; - for unlocker in utxo_unlockers.iter() { - let removal_record = mutator_set_accumulator - .drop(unlocker.mutator_set_item(), unlocker.mutator_set_mp()); - inputs.push(removal_record); - } - inputs - } - /// Generate a change UTXO to ensure that the difference in input amount /// and output amount goes back to us. Return the UTXO in a format compatible /// with claiming it later on, *i.e.*, as an [ExpectedUtxo]. @@ -795,8 +779,7 @@ impl GlobalState { // complete transaction kernel let removal_records = tx_inputs .iter() - .map(|txi| (txi.mutator_set_item(), txi.mutator_set_mp())) - .map(|(item, mp)| mutator_set_accumulator.drop(item, mp)) + .map(|txi| txi.removal_record(&mutator_set_accumulator)) .collect_vec(); let kernel = TransactionKernel { inputs: removal_records, @@ -1474,7 +1457,6 @@ mod global_state_tests { .wallet_state .wallet_secret .nth_generation_spending_key_for_tests(0); - let bob_address = bob_spending_key.to_address(); let genesis_block = Block::genesis_block(network); let alice_address = alice.nth_generation_spending_key_for_tests(0).to_address(); @@ -1504,7 +1486,7 @@ mod global_state_tests { .is_err()); // one month after though, we should be - let (tx, change_output) = bob + let (tx, _change_output) = bob .lock_guard() .await .create_transaction_with_prover_capability( @@ -1981,7 +1963,9 @@ mod global_state_tests { let genesis_state = genesis_state_lock.lock_guard().await; let (coinbase_transaction, coinbase_expected_utxo) = - make_coinbase_transaction(&genesis_state, NeptuneCoins::zero(), in_seven_months); + make_coinbase_transaction(&genesis_state, NeptuneCoins::zero(), in_seven_months) + .await + .unwrap(); drop(genesis_state); // Send two outputs each to Alice and Bob, from genesis receiver @@ -2067,9 +2051,10 @@ mod global_state_tests { .lock_guard_mut() .await .wallet_state - .add_expected_utxos(expected_utxos_for_alice); + .add_expected_utxos(expected_utxos_for_alice) + .await; - let expected_utxos_for_bob = bob_state_lock + let expected_utxos_for_bob_1 = bob_state_lock .lock_guard() .await .wallet_state @@ -2078,7 +2063,8 @@ mod global_state_tests { .lock_guard_mut() .await .wallet_state - .add_expected_utxos(expected_utxos_for_bob); + .add_expected_utxos(expected_utxos_for_bob_1) + .await; genesis_state_lock .lock_guard_mut() @@ -2168,8 +2154,7 @@ mod global_state_tests { .await .wallet_state .add_expected_utxos(expected_utxos_alice) - .await - .unwrap(); + .await; // make bob's transaction let tx_outputs_from_bob = vec![ @@ -2203,7 +2188,7 @@ mod global_state_tests { .unwrap(); // inform wallet of any expected utxos from this tx. - let expected_utxos_for_bob = bob_state_lock + let expected_utxos_for_bob_2 = bob_state_lock .lock_guard() .await .wallet_state @@ -2212,14 +2197,17 @@ mod global_state_tests { .lock_guard_mut() .await .wallet_state - .add_expected_utxos(expected_utxos_for_bob); + .add_expected_utxos(expected_utxos_for_bob_2) + .await; // Make block_2 with tx that contains: // - 4 inputs: 2 from Alice and 2 from Bob // - 6 outputs: 2 from Alice to Genesis, 3 from Bob to Genesis, and 1 coinbase to Genesis let genesis_state_mut = genesis_state_lock.lock_guard_mut().await; let (coinbase_transaction2, _expected_utxo) = - make_coinbase_transaction(&genesis_state_mut, NeptuneCoins::zero(), in_seven_months); + make_coinbase_transaction(&genesis_state_mut, NeptuneCoins::zero(), in_seven_months) + .await + .unwrap(); drop(genesis_state_mut); // don't care about change; testing correct constitution of blocks @@ -2433,8 +2421,7 @@ mod global_state_tests { alice_state_mut .wallet_state .add_expected_utxos(expected_utxo) - .await - .unwrap(); + .await; // the block gets mined. let block_1 = { diff --git a/src/models/state/transaction_details.rs b/src/models/state/transaction_details.rs index a90a4092..5fbc2ced 100644 --- a/src/models/state/transaction_details.rs +++ b/src/models/state/transaction_details.rs @@ -1,7 +1,7 @@ use anyhow::bail; use anyhow::Result; use num_traits::Zero; -use tracing::debug; +use tracing::error; use super::wallet::unlocked_utxo::UnlockedUtxo; use crate::models::blockchain::transaction::transaction_output::TxOutputList; @@ -33,11 +33,12 @@ impl TransactionDetails { pub(crate) fn new_with_coinbase( tx_inputs: Vec, tx_outputs: TxOutputList, - fee: NeptuneCoins, coinbase: NeptuneCoins, timestamp: Timestamp, mutator_set_accumulator: MutatorSetAccumulator, ) -> Result { + // Fee for coinbase txs is always zero + let fee = NeptuneCoins::zero(); Self::new( tx_inputs, tx_outputs, @@ -73,6 +74,13 @@ impl TransactionDetails { ) } + /// Constructor for TransactionDetails with some sanity checks. + /// + /// # Error + /// + /// Returns an error if (any of) + /// - the transaction is not balanced + /// - some mutator set membership proof is invalid. fn new( tx_inputs: Vec, tx_outputs: TxOutputList, @@ -82,20 +90,22 @@ impl TransactionDetails { mutator_set_accumulator: MutatorSetAccumulator, ) -> Result { // total amount to be spent -- determines how many and which UTXOs to use - let total_spent = tx_outputs.total_native_coins() + fee; + let total_spend = tx_outputs.total_native_coins() + fee; let total_input: NeptuneCoins = tx_inputs .iter() .map(|x| x.utxo.get_native_currency_amount()) .sum(); - let total_spendable = total_input + coinbase.unwrap_or(NeptuneCoins::zero()); + let coinbase_amount = coinbase.unwrap_or(NeptuneCoins::zero()); + let total_spendable = total_input + coinbase_amount; // sanity check: do we even have enough funds? - if total_spent > total_spendable { - debug!("Insufficient funds. total_spend: {total_spent}, total_spendable: {total_spendable}"); + if total_spend > total_spendable { + error!("Insufficient funds.\n\n total_spend: {total_spend}\ + \ntotal_spendable: {total_spendable}\ntotal_input: {total_input}\ncoinbase amount: {coinbase_amount}"); bail!("Not enough available funds."); } - if total_spent < total_spendable { - let diff = total_spent - total_spendable; + if total_spend < total_spendable { + let diff = total_spend - total_spendable; bail!("Missing change output in the amount of {}", diff); } if tx_inputs diff --git a/src/models/state/wallet/address/address_type.rs b/src/models/state/wallet/address/address_type.rs index 93c0a4e5..42129960 100644 --- a/src/models/state/wallet/address/address_type.rs +++ b/src/models/state/wallet/address/address_type.rs @@ -154,7 +154,7 @@ impl ReceivingAddress { /// /// Fields |0,1| enable the receiver to determine the ciphertext /// is intended for them and decryption should be attempted. - pub fn generate_public_announcement( + pub(crate) fn generate_public_announcement( &self, utxo_notification_payload: UtxoNotificationPayload, ) -> PublicAnnouncement { diff --git a/src/models/state/wallet/address/generation_address.rs b/src/models/state/wallet/address/generation_address.rs index 90dead55..56f47948 100644 --- a/src/models/state/wallet/address/generation_address.rs +++ b/src/models/state/wallet/address/generation_address.rs @@ -143,14 +143,6 @@ impl GenerationSpendingKey { fn generate_spending_lock(&self) -> Digest { self.unlock_key.hash() } - - pub(crate) fn try_decrypt_to_utxo_notification( - &self, - public_announcement: PublicAnnouncement, - ) -> Result { - let (utxo, sender_randomness) = self.decrypt(&public_announcement.message)?; - Ok(UtxoNotificationPayload::new(utxo, sender_randomness)) - } } impl GenerationReceivingAddress { diff --git a/src/models/state/wallet/mod.rs b/src/models/state/wallet/mod.rs index 60cbccce..35f5b776 100644 --- a/src/models/state/wallet/mod.rs +++ b/src/models/state/wallet/mod.rs @@ -448,7 +448,7 @@ mod wallet_tests { use crate::models::state::wallet::expected_utxo::UtxoNotifier; use crate::models::state::GlobalStateLock; use crate::tests::shared::make_mock_block; - use crate::tests::shared::make_mock_transaction_with_generation_key; + use crate::tests::shared::make_mock_transaction; use crate::tests::shared::mock_block_with_transaction; use crate::tests::shared::mock_genesis_global_state; use crate::tests::shared::mock_genesis_wallet_state; @@ -533,7 +533,7 @@ mod wallet_tests { } #[tokio::test] - async fn wallet_state_registration_of_monitored_utxos_test() { + async fn wallet_state_correctly_updates_monitored_and_expected_utxos() { let mut rng = thread_rng(); let network = Network::RegTest; let alice_wallet = WalletSecret::new_random(); @@ -590,7 +590,7 @@ mod wallet_tests { alice_expected_utxos[0].mined_in_block.unwrap().0, "Expected UTXO must be registered as being mined" ); - let mut alice_mutxos_block1 = get_monitored_utxos(&alice_wallet).await; + let alice_mutxos_block1 = get_monitored_utxos(&alice_wallet).await; assert_eq!( 1, alice_mutxos_block1.len(), @@ -598,40 +598,40 @@ mod wallet_tests { ); // Ensure that the membership proof is valid - let block_1_tx_output_digest = Hash::hash(&block_1_coinbase_utxo); - let ms_membership_proof = alice_mutxos_block1[0] + let alice_block_1_cb_item = Hash::hash(&block_1_coinbase_utxo); + let ms_membership_proof_block1 = alice_mutxos_block1[0] .get_membership_proof_for_block(block_1.hash()) .unwrap(); assert!(block_1 .kernel .body .mutator_set_accumulator - .verify(block_1_tx_output_digest, &ms_membership_proof)); + .verify(alice_block_1_cb_item, &ms_membership_proof_block1)); // Create new blocks, verify that the membership proofs are *not* valid // under this block as tip let (block_2, _, _) = make_mock_block(&block_1, None, bob_address, rng.gen()); let (block_3, _, _) = make_mock_block(&block_2, None, bob_address, rng.gen()); - let alice_mutxos = get_monitored_utxos(&alice_wallet).await; + + if ms_membership_proof_block1 + .auth_path_aocl + .authentication_path + .is_empty() { - let block_1_tx_output_digest = Hash::hash(&block_1_coinbase_utxo); - let ms_membership_proof = alice_mutxos[0] - .get_membership_proof_for_block(block_1.hash()) - .unwrap(); - let _membership_proof_is_valid = block_3 - .kernel - .body - .mutator_set_accumulator - .verify(block_1_tx_output_digest, &ms_membership_proof); - - // Actually, new blocks / transactions / UTXOs do not necessarily - // invalidate existing mutator set membership proofs (although that is - // what usually happens). So there is no point asserting it. - // assert!( - // !membership_proof_is_valid, - // "membership proof must be invalid before updating wallet state" - // ); + // We *know* that Alice's membership proof is invalid now, since + // the extra blocks would have invalidated the AOCL-MMR proof -- + // if the inserted UTXO was a peak (indicated by its authentication + // path having a length of zero). + assert!( + !block_3 + .kernel + .body + .mutator_set_accumulator + .verify(alice_block_1_cb_item, &ms_membership_proof_block1), + "membership proof must be invalid before updating wallet state" + ); } + // Verify that the membership proof is valid *after* running the updater alice_wallet .update_wallet_state_with_new_block( @@ -647,18 +647,19 @@ mod wallet_tests { ) .await .unwrap(); - alice_mutxos_block1 = get_monitored_utxos(&alice_wallet).await; + + let alice_mutxos_block3 = get_monitored_utxos(&alice_wallet).await; + assert_eq!(1, alice_mutxos_block3.len(), "Still only one MUTXO"); { - let block_1_tx_output_digest = Hash::hash(&block_1_coinbase_utxo); - let ms_membership_proof = alice_mutxos_block1[0] + let ms_membership_proof_block3 = alice_mutxos_block3[0] .get_membership_proof_for_block(block_3.hash()) .unwrap(); let membership_proof_is_valid = block_3 .kernel .body .mutator_set_accumulator - .verify(block_1_tx_output_digest, &ms_membership_proof); + .verify(alice_block_1_cb_item, &ms_membership_proof_block3); assert!( membership_proof_is_valid, "Membership proof must be valid after updating wallet state with generated blocks" @@ -827,15 +828,15 @@ mod wallet_tests { let msa_tip_previous = next_block.kernel.body.mutator_set_accumulator.clone(); let output_utxo = Utxo::new_native_currency(LockScript::anyone_can_spend(), NeptuneCoins::new(200)); - let tx_outputs = vec![TxOutput::no_notification(output_utxo, random(), random())].into(); + let tx_outputs: TxOutputList = + vec![TxOutput::no_notification(output_utxo, random(), random())].into(); - let tx = make_mock_transaction_with_generation_key( - tx_inputs_two_utxos, - tx_outputs, - NeptuneCoins::zero(), - msa_tip_previous.clone(), - ) - .await; + let removal_records = tx_inputs_two_utxos + .iter() + .map(|txi| txi.removal_record(&msa_tip_previous)) + .collect_vec(); + let addition_records = tx_outputs.addition_records(); + let tx = make_mock_transaction(removal_records, addition_records); let next_block = Block::new_block_from_template(&next_block.clone(), tx, now, None); assert_eq!( @@ -1186,7 +1187,9 @@ mod wallet_tests { let alice_state = alice.global_state_lock.lock_guard().await; let (coinbase_tx, cb_expected) = - make_coinbase_transaction(&alice_state, NeptuneCoins::zero(), in_seven_months); + make_coinbase_transaction(&alice_state, NeptuneCoins::zero(), in_seven_months) + .await + .unwrap(); let merged_tx = coinbase_tx.merge_with(tx_from_bob, Default::default()); let block_3_b = Block::new_block_from_template(&block_2_b, merged_tx, in_seven_months, None); @@ -1194,7 +1197,7 @@ mod wallet_tests { block_3_b.is_valid(&block_2_b, in_seven_months), "Block must be valid after accumulating txs" ); - let expected_utxo_for_alice = ExpectedUtxo::new( + let expected_utxo_for_alice_cb = ExpectedUtxo::new( cb_expected.utxo, cb_expected.sender_randomness, alice_spending_key.privacy_preimage, @@ -1206,7 +1209,7 @@ mod wallet_tests { .lock_guard_mut() .await .wallet_state - .add_expected_utxo(expected_utxo_for_alice) + .add_expected_utxo(expected_utxo_for_alice_cb) .await; let expected_utxo_for_alice = ExpectedUtxo::new( receiver_data_1_to_alice_new.utxo(), @@ -1323,17 +1326,17 @@ mod wallet_tests { #[tokio::test] async fn allow_consumption_of_genesis_output_test() { let network = Network::Main; - let bob = mock_genesis_wallet_state(WalletSecret::devnet_wallet(), network).await; - let bob_wallet = bob.wallet_secret; let genesis_block = Block::genesis_block(network); let in_seven_months = genesis_block.kernel.header.timestamp + Timestamp::months(7); - let bob = mock_genesis_global_state(network, 42, bob_wallet).await; + let bob = mock_genesis_global_state(network, 42, WalletSecret::devnet_wallet()).await; let bob = bob.lock_guard().await; let mut rng = StdRng::seed_from_u64(87255549301u64); let (cbtx, _cb_expected) = - make_coinbase_transaction(&bob, NeptuneCoins::zero(), in_seven_months); + make_coinbase_transaction(&bob, NeptuneCoins::zero(), in_seven_months) + .await + .unwrap(); let one_money: NeptuneCoins = NeptuneCoins::new(1); let anyone_can_spend_utxo = Utxo::new_native_currency(LockScript::anyone_can_spend(), one_money); diff --git a/src/models/state/wallet/unlocked_utxo.rs b/src/models/state/wallet/unlocked_utxo.rs index 8e5e6b2e..c6bf47ca 100644 --- a/src/models/state/wallet/unlocked_utxo.rs +++ b/src/models/state/wallet/unlocked_utxo.rs @@ -6,6 +6,8 @@ use crate::models::blockchain::transaction::lock_script::LockScriptAndWitness; use crate::models::blockchain::transaction::utxo::Utxo; use crate::tasm_lib::Digest; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; +use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; +use crate::util_types::mutator_set::removal_record::RemovalRecord; #[derive(Debug, Clone)] pub(crate) struct UnlockedUtxo { @@ -39,4 +41,10 @@ impl UnlockedUtxo { pub fn lock_script_and_witness(&self) -> &LockScriptAndWitness { &self.lock_script_and_witness } + + pub(crate) fn removal_record(&self, mutator_set: &MutatorSetAccumulator) -> RemovalRecord { + let item = self.mutator_set_item(); + let msmp = &self.membership_proof; + mutator_set.drop(item, msmp) + } } diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 7ac9abec..00460efd 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -278,7 +278,7 @@ impl WalletState { pub(crate) async fn add_expected_utxos( &mut self, expected_utxos: impl IntoIterator, - ) -> Result<()> { + ) { for expected_utxo in expected_utxos.into_iter() { self.add_expected_utxo(ExpectedUtxo::new( expected_utxo.utxo, @@ -288,7 +288,6 @@ impl WalletState { )) .await; } - Ok(()) } /// Return a list of UTXOs spent by this wallet in the transaction diff --git a/src/rpc_server.rs b/src/rpc_server.rs index b9a02804..b06f2635 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -705,14 +705,9 @@ impl RPC for NeptuneRPCServer { // Inform wallet of any expected incoming utxos. // note that this (briefly) mutates self. - if let Err(e) = gsm - .wallet_state + gsm.wallet_state .add_expected_utxos(utxos_sent_to_self) - .await - { - tracing::error!("Could not add expected utxos to wallet: {}", e); - return None; - } + .await; // ensure we write new wallet state out to disk. gsm.persist_wallet().await.expect("flushed wallet"); diff --git a/src/tests/shared.rs b/src/tests/shared.rs index d66637c6..9c28d0a8 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -53,27 +53,21 @@ use crate::models::blockchain::block::block_body::BlockBody; use crate::models::blockchain::block::block_header::BlockHeader; use crate::models::blockchain::block::block_header::TARGET_BLOCK_INTERVAL; use crate::models::blockchain::block::block_height::BlockHeight; -use crate::models::blockchain::block::block_kernel::BlockKernel; use crate::models::blockchain::block::mutator_set_update::MutatorSetUpdate; use crate::models::blockchain::block::Block; use crate::models::blockchain::block::BlockProof; -use crate::models::blockchain::transaction; use crate::models::blockchain::transaction::lock_script::LockScript; -use crate::models::blockchain::transaction::primitive_witness::SaltedUtxos; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_option; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_public_announcement; use crate::models::blockchain::transaction::transaction_kernel::pseudorandom_transaction_kernel; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; -use crate::models::blockchain::transaction::transaction_output::TxOutputList; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::blockchain::transaction::Transaction; use crate::models::blockchain::transaction::TransactionProof; -use crate::models::blockchain::type_scripts::native_currency::NativeCurrency; use crate::models::blockchain::type_scripts::neptune_coins::pseudorandom_amount; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::blockchain::type_scripts::time_lock::arbitrary_primitive_witness_with_expired_timelocks; -use crate::models::blockchain::type_scripts::TypeScriptAndWitness; use crate::models::channel::MainToPeerTask; use crate::models::channel::PeerTaskToMain; use crate::models::database::BlockIndexKey; @@ -83,7 +77,6 @@ use crate::models::peer::HandshakeData; use crate::models::peer::PeerInfo; use crate::models::peer::PeerMessage; use crate::models::peer::PeerStanding; -use crate::models::proof_abstractions::tasm::program::ConsensusProgram; use crate::models::proof_abstractions::timestamp::Timestamp; use crate::models::state::archival_state::ArchivalState; use crate::models::state::blockchain_state::BlockchainArchivalState; @@ -92,7 +85,8 @@ use crate::models::state::light_state::LightState; use crate::models::state::mempool::Mempool; use crate::models::state::networking_state::NetworkingState; use crate::models::state::wallet::address::generation_address; -use crate::models::state::wallet::unlocked_utxo::UnlockedUtxo; +use crate::models::state::wallet::expected_utxo::ExpectedUtxo; +use crate::models::state::wallet::expected_utxo::UtxoNotifier; use crate::models::state::wallet::wallet_state::WalletState; use crate::models::state::wallet::WalletSecret; use crate::models::state::GlobalStateLock; @@ -100,7 +94,6 @@ use crate::prelude::twenty_first; use crate::util_types::mutator_set::addition_record::pseudorandom_addition_record; use crate::util_types::mutator_set::addition_record::AdditionRecord; use crate::util_types::mutator_set::commit; -use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; use crate::util_types::mutator_set::removal_record::RemovalRecord; use crate::Hash; use crate::PEER_CHANNEL_CAPACITY; @@ -660,82 +653,6 @@ pub(crate) fn make_plenty_mock_transaction_with_primitive_witness( .collect_vec() } -// TODO: Consider moving this to to the appropriate place in global state, -// keep fn interface. Can be helper function to `create_transaction`. -pub(crate) async fn make_mock_transaction_with_generation_key( - utxo_unlockers: Vec, - tx_outputs: TxOutputList, - fee: NeptuneCoins, - tip_msa: MutatorSetAccumulator, -) -> Transaction { - // Generate removal records - let mut inputs = vec![]; - for unlocker in utxo_unlockers.iter() { - let removal_record = tip_msa.drop(unlocker.mutator_set_item(), unlocker.mutator_set_mp()); - inputs.push(removal_record); - } - - let mut outputs = vec![]; - let mut output_sender_randomnesses = vec![]; - let mut output_receiver_digests = vec![]; - for rd in tx_outputs.iter() { - let addition_record = commit( - Hash::hash(&rd.utxo), - rd.sender_randomness, - rd.receiver_privacy_digest, - ); - outputs.push(addition_record); - output_sender_randomnesses.push(rd.sender_randomness); - output_receiver_digests.push(rd.receiver_privacy_digest); - } - - let public_announcements = tx_outputs - .iter() - .map(|x| x.public_announcement.clone()) - .collect_vec(); - let timestamp = Timestamp::now(); - - let kernel = TransactionKernel { - inputs: tx_inputs.removal_records(&tip_msa), - outputs: tx_outputs.addition_records(), - public_announcements: tx_outputs.public_announcements(), - fee, - timestamp, - coinbase: None, - mutator_set_hash: tip_msa.hash(), - }; - - let input_utxos = utxo_unlockers - .iter() - .map(|unlocker| unlocker.utxo.clone()) - .collect_vec(); - let type_scripts_and_witnesses = vec![TypeScriptAndWitness::new(NativeCurrency.program())]; - let input_membership_proofs = utxo_unlockers - .iter() - .map(|unlocker| unlocker.mutator_set_mp()) - .cloned() - .collect_vec(); - let input_lock_scripts_and_witnesses = utxo_unlockers - .iter() - .map(|unlocker| unlocker.lock_script_and_witness().to_owned()) - .collect_vec(); - let output_utxos = tx_outputs.into_iter().map(|rd| rd.utxo).collect(); - let primitive_witness = transaction::primitive_witness::PrimitiveWitness { - input_utxos: SaltedUtxos::new(input_utxos), - type_scripts_and_witnesses, - lock_scripts_and_witnesses: input_lock_scripts_and_witnesses, - input_membership_proofs, - output_utxos: SaltedUtxos::new(output_utxos), - output_sender_randomnesses, - output_receiver_digests, - mutator_set_accumulator: tip_msa, - kernel: kernel.clone(), - }; - let proof = TransactionProof::Witness(primitive_witness); - - Transaction { kernel, proof } -} - /// Make a transaction with `Invalid` transaction proof. pub fn make_mock_transaction( inputs: Vec, @@ -764,7 +681,7 @@ pub(crate) fn dummy_expected_utxo() -> ExpectedUtxo { sender_randomness: Default::default(), receiver_preimage: Default::default(), received_from: UtxoNotifier::Myself, - notification_received: SystemTime::now(), + notification_received: Timestamp::now(), mined_in_block: None, } } @@ -833,7 +750,9 @@ pub(crate) fn mock_block_with_transaction( transaction.kernel.inputs.clone(), transaction.kernel.outputs.clone(), ); - ms_update.apply_to_accumulator(&mut next_mutator_set); + ms_update + .apply_to_accumulator(&mut next_mutator_set) + .unwrap(); let body = BlockBody { transaction_kernel: transaction.kernel, diff --git a/src/util_types/mutator_set/chunk_dictionary.rs b/src/util_types/mutator_set/chunk_dictionary.rs index c01a9241..42868760 100644 --- a/src/util_types/mutator_set/chunk_dictionary.rs +++ b/src/util_types/mutator_set/chunk_dictionary.rs @@ -14,7 +14,6 @@ use serde::Serialize; use tasm_lib::prelude::TasmObject; use tasm_lib::twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use triton_vm::prelude::Digest; -use twenty_first::math::b_field_element::BFieldElement; use twenty_first::math::bfield_codec::BFieldCodec; use twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof;