Skip to content

Commit

Permalink
test: Add proptest for fee gobbler
Browse files Browse the repository at this point in the history
Also:
 - Move structs related to UTXO notifications to separate file.
 - Implement `Arbitrary` for `ReceivingAddress`es (both).

Co-authored-by: Thorkil Schmidiger <[email protected]>
  • Loading branch information
aszepieniec and Sword-Smith committed Dec 19, 2024
1 parent 71f1cf6 commit 54ffdea
Show file tree
Hide file tree
Showing 20 changed files with 211 additions and 127 deletions.
2 changes: 1 addition & 1 deletion src/bin/dashboard_src/send_screen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crossterm::event::KeyEventKind;
use neptune_cash::config_models::network::Network;
use neptune_cash::models::blockchain::type_scripts::neptune_coins::NeptuneCoins;
use neptune_cash::models::state::wallet::address::ReceivingAddress;
use neptune_cash::models::state::wallet::transaction_output::UtxoNotificationMedium;
use neptune_cash::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use neptune_cash::rpc_server::RPCClient;
use num_traits::Zero;
use ratatui::layout::Alignment;
Expand Down
4 changes: 2 additions & 2 deletions src/bin/neptune-cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use neptune_cash::models::blockchain::type_scripts::neptune_coins::NeptuneCoins;
use neptune_cash::models::state::wallet::address::KeyType;
use neptune_cash::models::state::wallet::address::ReceivingAddress;
use neptune_cash::models::state::wallet::coin_with_possible_timelock::CoinWithPossibleTimeLock;
use neptune_cash::models::state::wallet::transaction_output::PrivateNotificationData;
use neptune_cash::models::state::wallet::transaction_output::UtxoNotificationMedium;
use neptune_cash::models::state::wallet::utxo_notification::PrivateNotificationData;
use neptune_cash::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use neptune_cash::models::state::wallet::wallet_status::WalletStatus;
use neptune_cash::models::state::wallet::WalletSecret;
use neptune_cash::rpc_server::RPCClient;
Expand Down
2 changes: 1 addition & 1 deletion src/main_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1684,7 +1684,7 @@ mod test {
use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins;
use crate::models::peer::transfer_transaction::TransactionProofQuality;
use crate::models::proof_abstractions::timestamp::Timestamp;
use crate::models::state::wallet::transaction_output::UtxoNotificationMedium;
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;

async fn a_transaction(
global_state_lock: &GlobalStateLock,
Expand Down
2 changes: 1 addition & 1 deletion src/mine_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -781,7 +781,7 @@ pub(crate) mod mine_loop_tests {
use crate::models::proof_abstractions::timestamp::Timestamp;
use crate::models::state::mempool::TransactionOrigin;
use crate::models::state::wallet::transaction_output::TxOutput;
use crate::models::state::wallet::transaction_output::UtxoNotificationMedium;
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use crate::tests::shared::dummy_expected_utxo;
use crate::tests::shared::make_mock_transaction_with_mutator_set_hash;
use crate::tests::shared::mock_genesis_global_state;
Expand Down
2 changes: 1 addition & 1 deletion src/models/blockchain/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@ mod block_tests {
use crate::models::blockchain::transaction::lock_script::LockScriptAndWitness;
use crate::models::state::tx_proving_capability::TxProvingCapability;
use crate::models::state::wallet::transaction_output::TxOutput;
use crate::models::state::wallet::transaction_output::UtxoNotificationMedium;
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use crate::models::state::wallet::WalletSecret;
use crate::tests::shared::invalid_block_with_transaction;
use crate::tests::shared::make_mock_block;
Expand Down
2 changes: 1 addition & 1 deletion src/models/state/archival_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1176,7 +1176,7 @@ mod archival_state_tests {
use crate::models::state::wallet::expected_utxo::UtxoNotifier;
use crate::models::state::wallet::transaction_output::TxOutput;
use crate::models::state::wallet::transaction_output::TxOutputList;
use crate::models::state::wallet::transaction_output::UtxoNotificationMedium;
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use crate::models::state::wallet::WalletSecret;
use crate::tests::shared::add_block_to_archival_state;
use crate::tests::shared::invalid_block_with_transaction;
Expand Down
2 changes: 1 addition & 1 deletion src/models/state/mempool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,7 @@ mod tests {
use crate::models::state::wallet::expected_utxo::UtxoNotifier;
use crate::models::state::wallet::transaction_output::TxOutput;
use crate::models::state::wallet::transaction_output::TxOutputList;
use crate::models::state::wallet::transaction_output::UtxoNotificationMedium;
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use crate::models::state::wallet::WalletSecret;
use crate::models::state::GlobalStateLock;
use crate::models::state::TritonVmJobQueue;
Expand Down
2 changes: 1 addition & 1 deletion src/models/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ use crate::models::state::wallet::expected_utxo::ExpectedUtxo;
use crate::models::state::wallet::monitored_utxo::MonitoredUtxo;
use crate::models::state::wallet::transaction_output::TxOutput;
use crate::models::state::wallet::transaction_output::TxOutputList;
use crate::models::state::wallet::transaction_output::UtxoNotificationMedium;
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use crate::prelude::twenty_first;
use crate::time_fn_call_async;
use crate::util_types::mutator_set::addition_record::AdditionRecord;
Expand Down
123 changes: 107 additions & 16 deletions src/models/state/transaction_details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use tasm_lib::prelude::Digest;
use tracing::error;

use super::wallet::transaction_output::TxOutput;
use super::wallet::transaction_output::UtxoNotifyMethod;
use super::wallet::unlocked_utxo::UnlockedUtxo;
use super::wallet::utxo_notification::UtxoNotifyMethod;
use crate::models::blockchain::block::MINING_REWARD_TIME_LOCK_PERIOD;
use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins;
use crate::models::proof_abstractions::timestamp::Timestamp;
Expand Down Expand Up @@ -45,24 +45,45 @@ impl TransactionDetails {
amount_liquid.div_two();
let amount_timelocked = gobbled_fee.checked_sub(&amount_liquid).unwrap();

let tx_outputs = vec![
TxOutput::native_currency(
amount_liquid,
sender_randomness,
notification_method.clone(),
true,
let (time_locked_txo, liquid_txo) = match notification_method {
UtxoNotifyMethod::OnChain(receiving_address) => (
TxOutput::onchain_native_currency(
amount_timelocked,
sender_randomness,
receiving_address.clone(),
true,
),
TxOutput::onchain_native_currency(
amount_liquid,
sender_randomness,
receiving_address,
true,
)
.with_time_lock(now + MINING_REWARD_TIME_LOCK_PERIOD),
),
TxOutput::native_currency(
amount_timelocked,
sender_randomness,
notification_method,
true,
)
.with_time_lock(now + MINING_REWARD_TIME_LOCK_PERIOD),
];
UtxoNotifyMethod::OffChain(receiving_address) => (
TxOutput::offchain_native_currency(
amount_timelocked,
sender_randomness,
receiving_address.clone(),
true,
),
TxOutput::offchain_native_currency(
amount_liquid,
sender_randomness,
receiving_address,
true,
)
.with_time_lock(now + MINING_REWARD_TIME_LOCK_PERIOD),
),
UtxoNotifyMethod::None => {
panic!("Cannot produce fee gobbler transaction without UTXO notification")
}
};

TransactionDetails::new_without_coinbase(
vec![],
tx_outputs.into(),
vec![time_locked_txo, liquid_txo].into(),
-gobbled_fee,
now,
mutator_set_accumulator,
Expand Down Expand Up @@ -172,3 +193,73 @@ impl TransactionDetails {
})
}
}

#[cfg(test)]
mod test {
use proptest_arbitrary_interop::arb;
use test_strategy::proptest;

use super::*;

#[proptest]
fn test_fee_gobbler_properties(
#[strategy(NeptuneCoins::arbitrary_non_negative())] gobbled_fee: NeptuneCoins,
#[strategy(arb())] sender_randomness: Digest,
#[strategy(arb())] mutator_set_accumulator: MutatorSetAccumulator,
#[strategy(arb())] now: Timestamp,
#[filter(#notification_method != UtxoNotifyMethod::None)]
#[strategy(arb())]
notification_method: UtxoNotifyMethod,
) {
let fee_gobbler = TransactionDetails::fee_gobbler(
gobbled_fee,
sender_randomness,
mutator_set_accumulator,
now,
notification_method,
);

assert!(
fee_gobbler.tx_inputs.is_empty(),
"fee gobbler must have no inputs"
);

assert_eq!(
NeptuneCoins::zero(),
fee_gobbler
.tx_outputs
.iter()
.map(|txo| txo.utxo().get_native_currency_amount())
.sum::<NeptuneCoins>()
+ fee_gobbler.fee,
"total transaction amount must be zero for fee gobbler"
);

assert!(
fee_gobbler.fee.is_negative() || fee_gobbler.fee.is_zero(),
"fee must be negative or zero; got {}",
fee_gobbler.fee
);

let mut half_of_fee = fee_gobbler.fee;
half_of_fee.div_two();

let time_locked_amount = fee_gobbler
.tx_outputs
.iter()
.map(|txo| txo.utxo())
.filter(|utxo| match utxo.release_date() {
Some(date) => date >= fee_gobbler.timestamp + MINING_REWARD_TIME_LOCK_PERIOD,
None => false,
})
.map(|utxo| utxo.get_native_currency_amount())
.sum::<NeptuneCoins>();
assert!(
-half_of_fee
<= time_locked_amount,
"at least half of negative-fee must be time-locked\nhalf of negative fee: {}\ntime-locked amount: {}",
-half_of_fee,
time_locked_amount,
);
}
}
5 changes: 3 additions & 2 deletions src/models/state/wallet/address/address_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use anyhow::bail;
use anyhow::Result;
use arbitrary::Arbitrary;
use serde::Deserialize;
use serde::Serialize;
use tasm_lib::triton_vm::prelude::Digest;
Expand All @@ -17,7 +18,7 @@ use crate::models::blockchain::transaction::transaction_kernel::TransactionKerne
use crate::models::blockchain::transaction::utxo::Utxo;
use crate::models::blockchain::transaction::AnnouncedUtxo;
use crate::models::blockchain::transaction::PublicAnnouncement;
use crate::models::state::wallet::transaction_output::UtxoNotificationPayload;
use crate::models::state::wallet::utxo_notification::UtxoNotificationPayload;
use crate::BFieldElement;

// note: assigning the flags to `KeyType` variants as discriminants has bonus
Expand Down Expand Up @@ -97,7 +98,7 @@ impl KeyType {
/// This enum provides an abstraction API for Address types, so that
/// a method or struct may simply accept a `ReceivingAddress` and be
/// forward-compatible with new types of Address as they are implemented.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Arbitrary)]
pub enum ReceivingAddress {
/// a [generation_address]
Generation(Box<generation_address::GenerationReceivingAddress>),
Expand Down
2 changes: 1 addition & 1 deletion src/models/state/wallet/address/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use twenty_first::math::tip5::Digest;
use crate::config_models::network::Network;
use crate::models::blockchain::shared::Hash;
use crate::models::blockchain::transaction::PublicAnnouncement;
use crate::models::state::wallet::transaction_output::UtxoNotificationPayload;
use crate::models::state::wallet::utxo_notification::UtxoNotificationPayload;
use crate::prelude::twenty_first;

/// returns human-readable-prefix for the given network
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use super::SpendingKey;
use crate::config_models::network::Network;
use crate::models::blockchain::transaction::PublicAnnouncement;
use crate::models::state::wallet::address::common::network_hrp_char;
use crate::models::state::wallet::transaction_output::UtxoNotificationPayload;
use crate::models::state::wallet::utxo_notification::UtxoNotificationPayload;

/// an encrypted wrapper for UTXO notifications.
///
Expand Down
10 changes: 9 additions & 1 deletion src/models/state/wallet/address/generation_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use aes_gcm::Aes256Gcm;
use aes_gcm::Nonce;
use anyhow::bail;
use anyhow::Result;
use arbitrary::Arbitrary;
use bech32::FromBase32;
use bech32::ToBase32;
use bech32::Variant;
Expand All @@ -38,7 +39,7 @@ use crate::models::blockchain::transaction::lock_script::LockScript;
use crate::models::blockchain::transaction::lock_script::LockScriptAndWitness;
use crate::models::blockchain::transaction::utxo::Utxo;
use crate::models::blockchain::transaction::PublicAnnouncement;
use crate::models::state::wallet::transaction_output::UtxoNotificationPayload;
use crate::models::state::wallet::utxo_notification::UtxoNotificationPayload;
use crate::prelude::twenty_first;

pub(super) const GENERATION_FLAG_U8: u8 = 79;
Expand Down Expand Up @@ -83,6 +84,13 @@ pub struct GenerationReceivingAddress {
pub spending_lock: Digest,
}

impl<'a> Arbitrary<'a> for GenerationReceivingAddress {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let seed = Digest::arbitrary(u)?;
Ok(Self::derive_from_seed(seed))
}
}

impl GenerationSpendingKey {
pub fn to_address(&self) -> GenerationReceivingAddress {
let randomness: [u8; 32] = common::shake256::<32>(&bincode::serialize(&self.seed).unwrap());
Expand Down
5 changes: 3 additions & 2 deletions src/models/state/wallet/address/symmetric_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use aes_gcm::Aes256Gcm;
use aes_gcm::Nonce;
use anyhow::bail;
use anyhow::Result;
use arbitrary::Arbitrary;
use bech32::FromBase32;
use bech32::ToBase32;
use serde::Deserialize;
Expand All @@ -23,7 +24,7 @@ use crate::models::blockchain::transaction::lock_script::LockScript;
use crate::models::blockchain::transaction::lock_script::LockScriptAndWitness;
use crate::models::blockchain::transaction::utxo::Utxo;
use crate::models::blockchain::transaction::PublicAnnouncement;
use crate::models::state::wallet::transaction_output::UtxoNotificationPayload;
use crate::models::state::wallet::utxo_notification::UtxoNotificationPayload;
use crate::prelude::twenty_first;

/// represents a symmetric key decryption error
Expand Down Expand Up @@ -78,7 +79,7 @@ pub const SYMMETRIC_KEY_FLAG: BFieldElement = BFieldElement::new(SYMMETRIC_KEY_F
///
/// The implementation can be easily changed later if needed as the type is
/// opaque.
#[derive(Clone, Debug, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Clone, Debug, Copy, Serialize, Deserialize, PartialEq, Eq, Arbitrary)]
pub struct SymmetricKey {
seed: Digest,
}
Expand Down
3 changes: 2 additions & 1 deletion src/models/state/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod monitored_utxo;
pub mod rusty_wallet_database;
pub mod transaction_output;
pub mod unlocked_utxo;
pub mod utxo_notification;
pub mod wallet_state;
pub mod wallet_status;

Expand Down Expand Up @@ -431,7 +432,7 @@ mod wallet_tests {
use crate::models::state::wallet::expected_utxo::UtxoNotifier;
use crate::models::state::wallet::transaction_output::TxOutput;
use crate::models::state::wallet::transaction_output::TxOutputList;
use crate::models::state::wallet::transaction_output::UtxoNotificationMedium;
use crate::models::state::wallet::utxo_notification::UtxoNotificationMedium;
use crate::models::state::GlobalStateLock;
use crate::tests::shared::invalid_block_with_transaction;
use crate::tests::shared::make_mock_block;
Expand Down
Loading

0 comments on commit 54ffdea

Please sign in to comment.