From 809817bb7df1fa6de3ecaed1f7b7875964446776 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 13 Mar 2024 14:20:06 +0100 Subject: [PATCH 1/4] refactor: Update `Network` enum Add variants `Beta` and `Main`. Add member function `launch_date` which is fixed for all but `RegTest` and for `RegTest` rounds now down to a block of 10 minutes. --- src/config_models/network.rs | 40 +++++++++++++++++++ .../wallet/address/generation_address.rs | 2 +- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/config_models/network.rs b/src/config_models/network.rs index a65d2b2f..7f6462dc 100644 --- a/src/config_models/network.rs +++ b/src/config_models/network.rs @@ -1,15 +1,51 @@ use serde::{Deserialize, Serialize}; use std::fmt; use std::str::FromStr; +use std::time::{SystemTime, UNIX_EPOCH}; use strum::EnumIter; +use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default, EnumIter)] pub enum Network { + /// First iteration of testnet. Not feature-complete. Soon to be deprecated. #[default] Alpha, + + /// Upcoming iteration of testnet. Not feature-complete either but moreso than + /// Alpha. Soon to be set as default. + Beta, + + /// Main net. Feature-complete. Fixed launch date. Not ready yet. + Main, + + /// Feature-complete (or as feature-complete as possible) test network separate + /// from whichever network is currently running. For integration tests involving + /// multiple nodes over a network. Testnet, + + /// Network for individual unit and integration tests. The timestamp for the + /// RegTest genesis block is set to now, rounded down to the first block of + /// 10 minutes. RegTest, } +impl Network { + pub(crate) fn launch_date(&self) -> BFieldElement { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + let ten_minutes = 1000 * 60 * 10; + let now_rounded = (now / ten_minutes) * ten_minutes; + match self { + // TODO: use now_rounded + Network::RegTest => BFieldElement::new(now_rounded), + // 1 July 2024 (might be revised though) + Network::Alpha | Network::Testnet | Network::Beta | Network::Main => { + BFieldElement::new(1719792000000u64) + } + } + } +} impl fmt::Display for Network { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -17,6 +53,8 @@ impl fmt::Display for Network { Network::Alpha => "alpha".to_string(), Network::Testnet => "testnet".to_string(), Network::RegTest => "regtest".to_string(), + Network::Beta => "beta".to_string(), + Network::Main => "main".to_string(), }; write!(f, "{}", string) } @@ -29,6 +67,8 @@ impl FromStr for Network { "alpha" => Ok(Network::Alpha), "testnet" => Ok(Network::Testnet), "regtest" => Ok(Network::RegTest), + "beta" => Ok(Network::Beta), + "main" => Ok(Network::Main), _ => Err(format!("Failed to parse {} as network", input)), } } diff --git a/src/models/state/wallet/address/generation_address.rs b/src/models/state/wallet/address/generation_address.rs index d17a8e5f..c993b6d3 100644 --- a/src/models/state/wallet/address/generation_address.rs +++ b/src/models/state/wallet/address/generation_address.rs @@ -389,7 +389,7 @@ impl ReceivingAddress { // NOLGA: Neptune lattice-based generation address let mut hrp = "nolga".to_string(); let network_byte: char = match network { - Network::Alpha => 'm', + Network::Alpha | Network::Beta | Network::Main => 'm', Network::Testnet => 't', Network::RegTest => 'r', }; From 4bc8c6acb41366dfe7f7123d8f2f10c77d39ffd6 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 13 Mar 2024 15:56:18 +0100 Subject: [PATCH 2/4] refactor: Make genesis block relative to `Network` --- src/bin/dashboard_src/overview_screen.rs | 2 +- src/config_models/network.rs | 5 +- src/lib.rs | 8 ++- src/mine_loop.rs | 5 +- src/models/blockchain/block/mod.rs | 62 ++++++++++++------------ src/models/state/archival_state.rs | 32 ++++++------ src/models/state/mempool.rs | 18 +++++-- src/models/state/mod.rs | 16 +++--- src/models/state/wallet/mod.rs | 22 ++++----- src/models/state/wallet/wallet_state.rs | 10 ++-- src/peer_loop.rs | 10 ++-- src/rpc_server.rs | 3 +- src/tests/shared.rs | 6 ++- 13 files changed, 112 insertions(+), 87 deletions(-) diff --git a/src/bin/dashboard_src/overview_screen.rs b/src/bin/dashboard_src/overview_screen.rs index 12cd3636..326859cf 100644 --- a/src/bin/dashboard_src/overview_screen.rs +++ b/src/bin/dashboard_src/overview_screen.rs @@ -105,7 +105,7 @@ impl OverviewData { is_mining: Some(false), syncing: false, block_header: Some( - neptune_core::models::blockchain::block::Block::genesis_block() + neptune_core::models::blockchain::block::Block::genesis_block(Network::Testnet) .kernel .header, ), diff --git a/src/config_models/network.rs b/src/config_models/network.rs index 7f6462dc..e56ba31d 100644 --- a/src/config_models/network.rs +++ b/src/config_models/network.rs @@ -25,7 +25,9 @@ pub enum Network { /// Network for individual unit and integration tests. The timestamp for the /// RegTest genesis block is set to now, rounded down to the first block of - /// 10 minutes. + /// 10 minutes. As a result, there is a small probability that tests fail + /// because they generate the genesis block twice on two opposite sides of a + /// round timestamp. RegTest, } impl Network { @@ -37,7 +39,6 @@ impl Network { let ten_minutes = 1000 * 60 * 10; let now_rounded = (now / ten_minutes) * ten_minutes; match self { - // TODO: use now_rounded Network::RegTest => BFieldElement::new(now_rounded), // 1 July 2024 (might be revised though) Network::Alpha | Network::Testnet | Network::Beta | Network::Main => { diff --git a/src/lib.rs b/src/lib.rs index 19424504..1248cedd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,13 @@ pub async fn initialize(cli_args: cli_args::Args) -> Result<()> { let archival_mutator_set = ArchivalState::initialize_mutator_set(&data_dir).await?; info!("Got archival mutator set"); - let archival_state = ArchivalState::new(data_dir, block_index_db, archival_mutator_set).await; + let archival_state = ArchivalState::new( + data_dir, + block_index_db, + archival_mutator_set, + cli_args.network, + ) + .await; // Get latest block. Use hardcoded genesis block if nothing is in database. let latest_block: Block = archival_state.get_latest_block().await; diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 1b458ec4..6c577b28 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -459,8 +459,9 @@ mod mine_loop_tests { #[tokio::test] async fn block_template_is_valid_test() -> Result<()> { // Verify that a block template made with transaction from the mempool is a valid block + let network = Network::RegTest; let premine_receiver_global_state_lock = - get_mock_global_state(Network::Alpha, 2, WalletSecret::devnet_wallet()).await; + get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; let mut premine_receiver_global_state = premine_receiver_global_state_lock.lock_guard_mut().await; assert!( @@ -469,7 +470,7 @@ mod mine_loop_tests { ); // Verify constructed coinbase transaction and block template when mempool is empty - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); let (transaction_empty_mempool, _coinbase_sender_randomness) = create_block_transaction(&genesis_block, &premine_receiver_global_state, now); diff --git a/src/models/blockchain/block/mod.rs b/src/models/blockchain/block/mod.rs index a284104f..8a81645b 100644 --- a/src/models/blockchain/block/mod.rs +++ b/src/models/blockchain/block/mod.rs @@ -124,16 +124,11 @@ impl Block { reward } - // 1 July 2024 (might be revised before then) - pub fn launch() -> u64 { - 1719792000000u64 - } - - pub fn genesis_block() -> Self { + pub fn genesis_block(network: Network) -> Self { let mut genesis_mutator_set = MutatorSetAccumulator::default(); let mut ms_update = MutatorSetUpdate::default(); - let premine_distribution = Self::premine_distribution(); + let premine_distribution = Self::premine_distribution(network); let total_premine_amount = premine_distribution .iter() .map(|(_receiving_address, amount)| *amount) @@ -144,7 +139,7 @@ impl Block { inputs: vec![], outputs: vec![], fee: NeptuneCoins::new(0), - timestamp: BFieldElement::new(Self::launch()), + timestamp: network.launch_date(), public_announcements: vec![], coinbase: Some(total_premine_amount), mutator_set_hash: MutatorSetAccumulator::new().hash(), @@ -158,8 +153,9 @@ impl Block { }, }; - for ((receiving_address, _amount), utxo) in - premine_distribution.iter().zip(Self::premine_utxos()) + for ((receiving_address, _amount), utxo) in premine_distribution + .iter() + .zip(Self::premine_utxos(network)) { let utxo_digest = Hash::hash(&utxo); // generate randomness for mutator set commitment @@ -188,7 +184,7 @@ impl Block { version: BFieldElement::zero(), height: BFieldElement::zero().into(), prev_block_digest: Digest::default(), - timestamp: BFieldElement::new(Self::launch()), + timestamp: network.launch_date(), nonce: [ BFieldElement::zero(), BFieldElement::zero(), @@ -203,7 +199,9 @@ impl Block { Self::new(header, body, None) } - fn premine_distribution() -> Vec<(generation_address::ReceivingAddress, NeptuneCoins)> { + fn premine_distribution( + _network: Network, + ) -> Vec<(generation_address::ReceivingAddress, NeptuneCoins)> { // The premine UTXOs can be hardcoded here. let authority_wallet = WalletSecret::devnet_wallet(); let authority_receiving_address = @@ -218,14 +216,14 @@ impl Block { ] } - pub fn premine_utxos() -> Vec { + pub fn premine_utxos(network: Network) -> Vec { let mut utxos = vec![]; - for (receiving_address, amount) in Self::premine_distribution() { + for (receiving_address, amount) in Self::premine_distribution(network) { // generate utxo let mut utxo = Utxo::new_native_coin(receiving_address.lock_script(), amount); let six_months = 365 * 24 * 60 * 60 * 1000 / 2; utxo.coins - .push(TimeLock::until(Self::launch() + six_months)); + .push(TimeLock::until(network.launch_date().value() + six_months)); utxos.push(utxo); } utxos @@ -370,11 +368,8 @@ impl Block { } // 0.f) Block timestamp is less than host-time (utc) + 2 hours. - // (but only check this if "now" is after launch) let future_limit = now + Duration::from_secs(60 * 60 * 2); - if now.as_millis() as u64 > Block::launch() - && (block_copy.kernel.header.timestamp.value() as u128) >= future_limit.as_millis() - { + if (block_copy.kernel.header.timestamp.value() as u128) >= future_limit.as_millis() { warn!("block time is too far in the future"); return false; } @@ -563,6 +558,7 @@ mod block_tests { }, tests::shared::{get_mock_global_state, make_mock_block, make_mock_block_with_valid_pow}, }; + use strum::IntoEnumIterator; use tasm_lib::twenty_first::{ storage::level_db::DB, util_types::{ @@ -583,7 +579,7 @@ mod block_tests { let mut rng = thread_rng(); // We need the global state to construct a transaction. This global state // has a wallet which receives a premine-UTXO. - let network = Network::Alpha; + let network = Network::RegTest; let global_state_lock = get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; let spending_key = global_state_lock @@ -597,7 +593,7 @@ mod block_tests { let other_address = other_wallet_secret .nth_generation_spending_key(0) .to_address(); - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let (mut block_1, _, _) = make_mock_block(&genesis_block, None, address, rng.gen()); let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); @@ -689,7 +685,8 @@ mod block_tests { #[test] fn block_with_wrong_mmra_is_invalid() { let mut rng = thread_rng(); - let genesis_block = Block::genesis_block(); + let network = Network::RegTest; + let genesis_block = Block::genesis_block(network); let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); @@ -706,7 +703,8 @@ mod block_tests { #[test] fn block_with_far_future_timestamp_is_invalid() { let mut rng = thread_rng(); - let genesis_block = Block::genesis_block(); + let network = Network::RegTest; + let genesis_block = Block::genesis_block(network); let mut now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); let a_wallet_secret = WalletSecret::new_random(); @@ -744,7 +742,8 @@ mod block_tests { #[test] fn can_prove_block_ancestry() { let mut rng = thread_rng(); - let genesis_block = Block::genesis_block(); + let network = Network::RegTest; + let genesis_block = Block::genesis_block(network); let mut blocks = vec![]; blocks.push(genesis_block.clone()); let db = DB::open_new_test_database(true, None, None, None).unwrap(); @@ -806,13 +805,14 @@ mod block_tests { // 831600 = 42000000 * 0.0198 // where 42000000 is the asymptotical limit of the token supply // and 1.98% is the relative size of the premine - let premine_max_size = NeptuneCoins::new(831600); - - let total_premine = Block::premine_distribution() - .iter() - .map(|(_receiving_address, amount)| *amount) - .sum::(); + for network in Network::iter() { + let premine_max_size = NeptuneCoins::new(831600); + let total_premine = Block::premine_distribution(network) + .iter() + .map(|(_receiving_address, amount)| *amount) + .sum::(); - assert!(total_premine <= premine_max_size); + assert!(total_premine <= premine_max_size); + } } } diff --git a/src/models/state/archival_state.rs b/src/models/state/archival_state.rs index 35fa69fd..7f3e8b6e 100644 --- a/src/models/state/archival_state.rs +++ b/src/models/state/archival_state.rs @@ -1,3 +1,4 @@ +use crate::config_models::network::Network; use crate::prelude::twenty_first; use anyhow::Result; @@ -191,8 +192,9 @@ impl ArchivalState { data_dir: DataDirectory, block_index_db: NeptuneLevelDb, mut archival_mutator_set: RustyArchivalMutatorSet, + network: Network, ) -> Self { - let genesis_block = Box::new(Block::genesis_block()); + let genesis_block = Box::new(Block::genesis_block(network)); // If archival mutator set is empty, populate it with the addition records from genesis block // This assumes genesis block doesn't spend anything -- which it can't so that should be OK. @@ -821,7 +823,7 @@ mod archival_state_tests { .await .unwrap(); - ArchivalState::new(data_dir, block_index_db, ams).await + ArchivalState::new(data_dir, block_index_db, ams, network).await } #[traced_test] @@ -831,11 +833,11 @@ mod archival_state_tests { let seed: [u8; 32] = thread_rng().gen(); tokio::spawn(async move { let mut rng: StdRng = SeedableRng::from_seed(seed); - let network = Network::Alpha; + let network = Network::RegTest; let mut archival_state0 = make_test_archival_state(network).await; - let b = Block::genesis_block(); + let b = Block::genesis_block(network); let some_wallet_secret = WalletSecret::new_random(); let some_spending_key = some_wallet_secret.nth_generation_spending_key(0); let some_receiving_address = some_spending_key.to_address(); @@ -860,10 +862,11 @@ mod archival_state_tests { #[tokio::test] async fn archival_state_init_test() -> Result<()> { // Verify that archival mutator set is populated with outputs from genesis block - let archival_state = make_test_archival_state(Network::Alpha).await; + let network = Network::RegTest; + let archival_state = make_test_archival_state(network).await; assert_eq!( - Block::genesis_block() + Block::genesis_block(network) .kernel .body .transaction @@ -880,7 +883,7 @@ mod archival_state_tests { ); assert_eq!( - Block::genesis_block().hash(), + Block::genesis_block(network).hash(), archival_state.archival_mutator_set.get_sync_label(), "AMS must be synced to genesis block after initialization from genesis block" ); @@ -1093,7 +1096,7 @@ mod archival_state_tests { // Make a rollback of one block that contains multiple inputs and outputs. // This test is intended to verify that rollbacks work for non-trivial // blocks. - let network = Network::Alpha; + let network = Network::RegTest; let (mut archival_state, _peer_db_lock, _data_dir) = make_unit_test_archival_state(network).await; let genesis_wallet_state = @@ -1101,7 +1104,7 @@ mod archival_state_tests { let genesis_wallet = genesis_wallet_state.wallet_secret; let own_receiving_address = genesis_wallet.nth_generation_spending_key(0).to_address(); let global_state_lock = get_mock_global_state(Network::RegTest, 42, genesis_wallet).await; - let mut num_utxos = Block::premine_utxos().len(); + let mut num_utxos = Block::premine_utxos(network).len(); // 1. Create new block 1 with one input and four outputs and store it to disk let (mut block_1a, _, _) = make_mock_block_with_valid_pow( @@ -1221,15 +1224,16 @@ mod archival_state_tests { // This test is intended to verify that rollbacks work for non-trivial // blocks, also when there are many blocks that push the active window of the // mutator set forwards. + let network = Network::RegTest; let genesis_wallet_state = - get_mock_wallet_state(WalletSecret::devnet_wallet(), Network::Alpha).await; + get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let genesis_wallet = genesis_wallet_state.wallet_secret; let own_receiving_address = genesis_wallet.nth_generation_spending_key(0).to_address(); let global_state_lock = get_mock_global_state(Network::RegTest, 42, genesis_wallet).await; let mut global_state = global_state_lock.lock_guard_mut().await; let genesis_block: Block = *global_state.chain.archival_state().genesis_block.to_owned(); - let mut num_utxos = Block::premine_utxos().len(); + let mut num_utxos = Block::premine_utxos(network).len(); let mut previous_block = genesis_block.clone(); // this variable might come in handy for reporting purposes @@ -1397,7 +1401,7 @@ mod archival_state_tests { get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let genesis_wallet = genesis_wallet_state.wallet_secret; let own_receiving_address = genesis_wallet.nth_generation_spending_key(0).to_address(); - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); let (mut block_1_a, _, _) = @@ -1442,7 +1446,7 @@ mod archival_state_tests { async fn allow_multiple_inputs_and_outputs_in_block() { let mut rng = thread_rng(); // Test various parts of the state update when a block contains multiple inputs and outputs - let network = Network::Alpha; + let network = Network::RegTest; let genesis_wallet_state = get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let genesis_spending_key = genesis_wallet_state @@ -1459,7 +1463,7 @@ mod archival_state_tests { let bob_spending_key = wallet_secret_bob.nth_generation_spending_key(0); let bob_state_lock = get_mock_global_state(network, 3, wallet_secret_bob).await; - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let launch = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); diff --git a/src/models/state/mempool.rs b/src/models/state/mempool.rs index e716e0f1..b65d4931 100644 --- a/src/models/state/mempool.rs +++ b/src/models/state/mempool.rs @@ -559,9 +559,10 @@ mod tests { let mut rng: StdRng = SeedableRng::from_seed(seed); // We need the global state to construct a transaction. This global state // has a wallet which receives a premine-UTXO. + let network = Network::RegTest; let devnet_wallet = WalletSecret::devnet_wallet(); let premine_receiver_global_state_lock = - get_mock_global_state(Network::Alpha, 2, devnet_wallet).await; + get_mock_global_state(network, 2, devnet_wallet).await; let mut premine_receiver_global_state = premine_receiver_global_state_lock.lock_guard_mut().await; @@ -571,13 +572,13 @@ mod tests { let other_wallet_secret = WalletSecret::new_pseudorandom(rng.gen()); let other_global_state_lock = - get_mock_global_state(Network::Alpha, 2, other_wallet_secret.clone()).await; + get_mock_global_state(network, 2, other_wallet_secret.clone()).await; let mut other_global_state = other_global_state_lock.lock_guard_mut().await; let other_receiver_spending_key = other_wallet_secret.nth_generation_spending_key(0); let other_receiver_address = other_receiver_spending_key.to_address(); // Ensure that both wallets have a non-zero balance - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let (block_1, coinbase_utxo_1, cb_sender_randomness_1) = make_mock_block(&genesis_block, None, other_receiver_address, rng.gen()); @@ -772,9 +773,16 @@ mod tests { #[tokio::test] async fn conflicting_txs_preserve_highest_fee() -> Result<()> { // Create a global state object, controlled by a preminer who receives a premine-UTXO. + let network = Network::RegTest; let preminer_state_lock = - get_mock_global_state(Network::Alpha, 2, WalletSecret::devnet_wallet()).await; - let now = Duration::from_millis(Block::genesis_block().kernel.header.timestamp.value()); + get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; + let now = Duration::from_millis( + Block::genesis_block(network) + .kernel + .header + .timestamp + .value(), + ); let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); let mut preminer_state = preminer_state_lock.lock_guard_mut().await; let premine_wallet_secret = &preminer_state.wallet_state.wallet_secret; diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index 1d07db7b..34f5ca95 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -1286,11 +1286,11 @@ mod global_state_tests { #[traced_test] #[tokio::test] async fn premine_recipient_cannot_spend_premine_before_and_can_after_release_date() { - let network = Network::Alpha; + let network = Network::RegTest; let other_wallet = WalletSecret::new_random(); let global_state_lock = get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let twenty_neptune: NeptuneCoins = NeptuneCoins::new(20); let twenty_coins = twenty_neptune.to_native_coins(); let recipient_address = other_wallet.nth_generation_spending_key(0).to_address(); @@ -1413,14 +1413,14 @@ mod global_state_tests { #[tokio::test] async fn restore_monitored_utxos_from_recovery_data_test() { let mut rng = thread_rng(); - let network = Network::Alpha; + let network = Network::RegTest; let devnet_wallet = WalletSecret::devnet_wallet(); let global_state_lock = get_mock_global_state(network, 2, devnet_wallet).await; let mut global_state = global_state_lock.lock_guard_mut().await; let other_receiver_address = WalletSecret::new_random() .nth_generation_spending_key(0) .to_address(); - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let (mock_block_1, _, _) = make_mock_block(&genesis_block, None, other_receiver_address, rng.gen()); crate::tests::shared::add_block_to_archival_state( @@ -1501,7 +1501,7 @@ mod global_state_tests { .to_address(); // 1. Create new block 1 and store it to the DB - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let launch = genesis_block.kernel.header.timestamp.value(); let seven_months = 7 * 30 * 24 * 60 * 60 * 1000; let (mock_block_1a, _, _) = @@ -1911,7 +1911,7 @@ mod global_state_tests { let mut rng: StdRng = SeedableRng::from_seed(seed); // Test various parts of the state update when a block contains multiple inputs and outputs - let network = Network::Alpha; + let network = Network::RegTest; let genesis_wallet_state = get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let genesis_spending_key = genesis_wallet_state @@ -1928,7 +1928,7 @@ mod global_state_tests { let bob_spending_key = wallet_secret_bob.nth_generation_spending_key(0); let bob_state_lock = get_mock_global_state(network, 3, wallet_secret_bob).await; - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let launch = genesis_block.kernel.header.timestamp.value(); let seven_months = 7 * 30 * 24 * 60 * 60 * 1000; @@ -2214,7 +2214,7 @@ mod global_state_tests { let global_state_lock = get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; let mut global_state = global_state_lock.lock_guard_mut().await; - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); let wallet_secret = WalletSecret::new_random(); diff --git a/src/models/state/wallet/mod.rs b/src/models/state/wallet/mod.rs index 4d203392..1c44cd8e 100644 --- a/src/models/state/wallet/mod.rs +++ b/src/models/state/wallet/mod.rs @@ -382,7 +382,7 @@ mod wallet_tests { let mut rng = thread_rng(); // This test is designed to verify that the genesis block is applied // to the wallet state at initialization. - let network = Network::Testnet; + let network = Network::RegTest; let mut wallet_state_premine_recipient = get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let monitored_utxos_premine_wallet = @@ -393,7 +393,7 @@ mod wallet_tests { "Monitored UTXO list must contain premined UTXO at init, for premine-wallet" ); - let expected_premine_utxo = Block::premine_utxos()[0].clone(); + let expected_premine_utxo = Block::premine_utxos(network)[0].clone(); assert_eq!( expected_premine_utxo, monitored_utxos_premine_wallet[0].utxo, "Auth wallet's monitored UTXO must match that from genesis block at initialization" @@ -408,7 +408,7 @@ mod wallet_tests { ); // Add 12 blocks and verify that membership proofs are still valid - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let mut next_block = genesis_block.clone(); let other_wallet_secret = WalletSecret::new_random(); let other_receiver_address = other_wallet_secret @@ -452,7 +452,7 @@ mod wallet_tests { #[tokio::test] async fn wallet_state_registration_of_monitored_utxos_test() -> Result<()> { let mut rng = thread_rng(); - let network = Network::Testnet; + let network = Network::RegTest; let own_wallet_secret = WalletSecret::new_random(); let mut own_wallet_state = get_mock_wallet_state(own_wallet_secret.clone(), network).await; let other_wallet_secret = WalletSecret::new_random(); @@ -466,7 +466,7 @@ mod wallet_tests { "Monitored UTXO list must be empty at init" ); - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let own_spending_key = own_wallet_secret.nth_generation_spending_key(0); let own_recipient_address = own_spending_key.to_address(); let (block_1, block_1_coinbase_utxo, block_1_coinbase_sender_randomness) = @@ -534,7 +534,7 @@ mod wallet_tests { let ms_membership_proof = monitored_utxos[0] .get_membership_proof_for_block(block_1.hash()) .unwrap(); - let membership_proof_is_valid = block_3 + let _membership_proof_is_valid = block_3 .kernel .body .mutator_set_accumulator @@ -587,12 +587,12 @@ mod wallet_tests { async fn allocate_sufficient_input_funds_test() -> Result<()> { let mut rng = thread_rng(); let own_wallet_secret = WalletSecret::new_random(); - let network = Network::Testnet; + let network = Network::RegTest; let mut own_wallet_state = get_mock_wallet_state(own_wallet_secret, network).await; let own_spending_key = own_wallet_state .wallet_secret .nth_generation_spending_key(0); - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let (block_1, cb_utxo, cb_output_randomness) = make_mock_block( &genesis_block, None, @@ -803,19 +803,19 @@ mod wallet_tests { // An archival state is needed for how we currently add inputs to a transaction. // So it's just used to generate test data, not in any of the functions that are // actually tested. - let network = Network::Alpha; + let network = Network::RegTest; let own_wallet_secret = WalletSecret::new_random(); let mut own_wallet_state = get_mock_wallet_state(own_wallet_secret, network).await; let own_spending_key = own_wallet_state .wallet_secret .nth_generation_spending_key(0); let own_address = own_spending_key.to_address(); - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let premine_wallet = get_mock_wallet_state(WalletSecret::devnet_wallet(), network) .await .wallet_secret; let premine_receiver_global_state_lock = - get_mock_global_state(Network::Alpha, 2, premine_wallet).await; + get_mock_global_state(network, 2, premine_wallet).await; let mut premine_receiver_global_state = premine_receiver_global_state_lock.lock_guard_mut().await; let launch = genesis_block.kernel.header.timestamp.value(); diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 7358410f..3a7184f1 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -203,7 +203,7 @@ impl WalletState { // Check if we are premine recipients let own_spending_key = wallet_state.wallet_secret.nth_generation_spending_key(0); let own_receiving_address = own_spending_key.to_address(); - for utxo in Block::premine_utxos() { + for utxo in Block::premine_utxos(cli_args.network) { if utxo.lock_script_hash == own_receiving_address.lock_script().hash() { wallet_state .expected_utxos @@ -220,7 +220,7 @@ impl WalletState { wallet_state .update_wallet_state_with_new_block( &MutatorSetAccumulator::default(), - &Block::genesis_block(), + &Block::genesis_block(cli_args.network), ) .await .expect("Updating wallet state with genesis block must succeed"); @@ -771,12 +771,12 @@ mod tests { // Prune // Verify that MUTXO *is* marked as abandoned - let network = Network::Testnet; + let network = Network::RegTest; let own_wallet_secret = WalletSecret::new_random(); let own_spending_key = own_wallet_secret.nth_generation_spending_key(0); let own_global_state_lock = get_mock_global_state(network, 0, own_wallet_secret).await; let mut own_global_state = own_global_state_lock.lock_guard_mut().await; - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let monitored_utxos_count_init = own_global_state .wallet_state .wallet_db @@ -1054,7 +1054,7 @@ mod tests { async fn mock_wallet_state_is_synchronized_to_genesis_block() { let network = Network::RegTest; let wallet = WalletSecret::devnet_wallet(); - let genesis_block = Block::genesis_block(); + let genesis_block = Block::genesis_block(network); let wallet_state = get_mock_wallet_state(wallet, network).await; diff --git a/src/peer_loop.rs b/src/peer_loop.rs index 12bd0205..2bd76a07 100644 --- a/src/peer_loop.rs +++ b/src/peer_loop.rs @@ -1732,10 +1732,11 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_peer_loop_receival_of_first_block() -> Result<()> { + let network = Network::RegTest; let mut rng = thread_rng(); // Scenario: client only knows genesis block. Then receives block 1. let (_peer_broadcast_tx, from_main_rx_clone, to_main_tx, mut to_main_rx1, state_lock, hsd) = - get_test_genesis_setup(Network::Alpha, 0).await?; + get_test_genesis_setup(network, 0).await?; let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let peer_address = get_dummy_socket_address(0); @@ -1796,7 +1797,7 @@ mod peer_loop_tests { let mut rng = thread_rng(); // In this scenario, the client only knows the genesis block (block 0) and then // receives block 2, meaning that block 1 will have to be requested. - let network = Network::Testnet; + let network = Network::RegTest; let (_peer_broadcast_tx, from_main_rx_clone, to_main_tx, mut to_main_rx1, state_lock, hsd) = get_test_genesis_setup(network, 0).await?; let peer_address = get_dummy_socket_address(0); @@ -1866,6 +1867,7 @@ mod peer_loop_tests { #[tokio::test] async fn prevent_ram_exhaustion_test() -> Result<()> { let mut rng = thread_rng(); + let network = Network::RegTest; // In this scenario the peer sends more blocks than the client allows to store in the // fork-reconciliation field. This should result in abandonment of the fork-reconciliation // process as the alternative is that the program will crash because it runs out of RAM. @@ -1876,7 +1878,7 @@ mod peer_loop_tests { mut to_main_rx1, mut state_lock, _hsd, - ) = get_test_genesis_setup(Network::Alpha, 1).await?; + ) = get_test_genesis_setup(network, 1).await?; // Restrict max number of blocks held in memory to 2. let mut cli = state_lock.cli().clone(); @@ -1980,7 +1982,7 @@ mod peer_loop_tests { let mut rng = thread_rng(); // In this scenario, the client know the genesis block (block 0) and block 1, it // then receives block 4, meaning that block 3 and 2 will have to be requested. - let network = Network::Testnet; + let network = Network::RegTest; let (_peer_broadcast_tx, from_main_rx_clone, to_main_tx, mut to_main_rx1, state_lock, hsd) = get_test_genesis_setup(network, 0).await?; let mut global_state_mut = state_lock.lock_guard_mut().await; diff --git a/src/rpc_server.rs b/src/rpc_server.rs index c2ea18b6..345090d3 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -705,7 +705,8 @@ mod rpc_server_tests { // We don't care about the actual response data in this test, just that the // requests do not crash the server. - let (rpc_server, _) = test_rpc_server(Network::Alpha, WalletSecret::new_random(), 2).await; + let network = Network::RegTest; + let (rpc_server, _) = test_rpc_server(network, WalletSecret::new_random(), 2).await; let ctx = context::current(); let _ = rpc_server.clone().network(ctx).await; let _ = rpc_server.clone().own_listen_address_for_peers(ctx).await; diff --git a/src/tests/shared.rs b/src/tests/shared.rs index 966a9d0b..6217c75e 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -144,8 +144,9 @@ pub fn get_dummy_version() -> String { pub fn get_dummy_latest_block( input_block: Option, ) -> (Block, LatestBlockInfo, Arc>) { + let network = Network::RegTest; let block = match input_block { - None => Block::genesis_block(), + None => Block::genesis_block(network), Some(block) => block, }; @@ -1046,6 +1047,7 @@ pub fn make_mock_block_with_invalid_pow( pub async fn get_mock_wallet_state(wallet_secret: WalletSecret, network: Network) -> WalletState { let cli_args: cli_args::Args = cli_args::Args { number_of_mps_per_utxo: 30, + network, ..Default::default() }; let data_dir = unit_test_data_directory(network).unwrap(); @@ -1061,7 +1063,7 @@ pub async fn make_unit_test_archival_state( .await .unwrap(); - let archival_state = ArchivalState::new(data_dir.clone(), block_index_db, ams).await; + let archival_state = ArchivalState::new(data_dir.clone(), block_index_db, ams, network).await; (archival_state, peer_db, data_dir) } From 9e55a5b62afec6922155b008588480cdd0d235cd Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Wed, 13 Mar 2024 18:04:27 +0100 Subject: [PATCH 3/4] refactor: harmonize timestamps Introduces a new struct, `Timestamp`, for consensus-critical timestamps. `Timestamp` is a wrapper around a `BFieldElement` which counts the number of milliseconds elapsed since the Unix epoch. Closes #117. --- src/bin/dashboard_src/history_screen.rs | 9 +- src/config_models/network.rs | 8 +- src/main_loop.rs | 5 +- src/mine_loop.rs | 36 ++--- src/models/blockchain/block/block_header.rs | 3 +- src/models/blockchain/block/mod.rs | 80 +++++----- src/models/blockchain/transaction/mod.rs | 27 +--- .../transaction/primitive_witness.rs | 11 +- .../transaction/transaction_kernel.rs | 18 +-- src/models/blockchain/transaction/utxo.rs | 25 ++-- src/models/blockchain/transaction/validity.rs | 4 +- .../blockchain/type_scripts/time_lock.rs | 38 ++--- src/models/consensus/mod.rs | 1 + src/models/consensus/timestamp.rs | 141 ++++++++++++++++++ src/models/database.rs | 24 ++- src/models/state/archival_state.rs | 46 +++--- src/models/state/mempool.rs | 41 ++--- src/models/state/mod.rs | 51 ++++--- .../wallet/coin_with_possible_timelock.rs | 30 ++-- src/models/state/wallet/mod.rs | 15 +- src/models/state/wallet/monitored_utxo.rs | 10 +- src/models/state/wallet/wallet_state.rs | 15 +- src/models/state/wallet/wallet_status.rs | 11 +- src/peer_loop.rs | 29 ++-- src/rpc_server.rs | 27 +--- src/tests/shared.rs | 51 ++----- 26 files changed, 397 insertions(+), 359 deletions(-) create mode 100644 src/models/consensus/timestamp.rs diff --git a/src/bin/dashboard_src/history_screen.rs b/src/bin/dashboard_src/history_screen.rs index 10aaab2d..579a6bbd 100644 --- a/src/bin/dashboard_src/history_screen.rs +++ b/src/bin/dashboard_src/history_screen.rs @@ -8,8 +8,9 @@ use super::{dashboard_app::DashboardEvent, screen::Screen}; use crossterm::event::{Event, KeyCode, KeyEventKind}; use itertools::Itertools; use neptune_core::{ - models::blockchain::{ - block::block_height::BlockHeight, type_scripts::neptune_coins::NeptuneCoins, + models::{ + blockchain::{block::block_height::BlockHeight, type_scripts::neptune_coins::NeptuneCoins}, + consensus::timestamp::Timestamp, }, rpc_server::RPCClient, }; @@ -24,7 +25,7 @@ use tokio::time::sleep; use tokio::{select, task::JoinHandle}; use unicode_width::UnicodeWidthStr; -type BalanceUpdate = (BlockHeight, Duration, NeptuneCoins, NeptuneCoins); +type BalanceUpdate = (BlockHeight, Timestamp, NeptuneCoins, NeptuneCoins); type BalanceUpdateArc = Arc>>; type DashboardEventArc = Arc>>; type JoinHandleArc = Arc>>; @@ -269,7 +270,7 @@ impl Widget for HistoryScreen { let (height, timestamp, amount, balance) = *bu; vec![ height.to_string(), - neptune_core::utc_timestamp_to_localtime(timestamp.as_millis()).to_string(), + timestamp.standard_format(), if !amount.is_negative() { "⭸".to_string() } else { diff --git a/src/config_models/network.rs b/src/config_models/network.rs index e56ba31d..2101e8eb 100644 --- a/src/config_models/network.rs +++ b/src/config_models/network.rs @@ -5,6 +5,8 @@ use std::time::{SystemTime, UNIX_EPOCH}; use strum::EnumIter; use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; +use crate::models::consensus::timestamp::Timestamp; + #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Default, EnumIter)] pub enum Network { /// First iteration of testnet. Not feature-complete. Soon to be deprecated. @@ -31,7 +33,7 @@ pub enum Network { RegTest, } impl Network { - pub(crate) fn launch_date(&self) -> BFieldElement { + pub(crate) fn launch_date(&self) -> Timestamp { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -39,10 +41,10 @@ impl Network { let ten_minutes = 1000 * 60 * 10; let now_rounded = (now / ten_minutes) * ten_minutes; match self { - Network::RegTest => BFieldElement::new(now_rounded), + Network::RegTest => Timestamp(BFieldElement::new(now_rounded)), // 1 July 2024 (might be revised though) Network::Alpha | Network::Testnet | Network::Beta | Network::Main => { - BFieldElement::new(1719792000000u64) + Timestamp(BFieldElement::new(1719792000000u64)) } } } diff --git a/src/main_loop.rs b/src/main_loop.rs index edaf3993..39162ba5 100644 --- a/src/main_loop.rs +++ b/src/main_loop.rs @@ -415,10 +415,7 @@ impl MainLoopHandler { "Storing block {} in database. Height: {}, Mined: {}", new_block.hash().emojihash(), new_block.kernel.header.height, - crate::utc_timestamp_to_localtime( - new_block.kernel.header.timestamp.value() - ) - .to_string() + new_block.kernel.header.timestamp.standard_format() ); global_state_mut.store_block(new_block).await?; diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 6c577b28..e398f766 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -13,6 +13,7 @@ use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::blockchain::type_scripts::TypeScript; use crate::models::channel::*; use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::timestamp::Timestamp; use crate::models::shared::SIZE_20MB_IN_BYTES; use crate::models::state::wallet::utxo_notification_pool::{ExpectedUtxo, UtxoNotifier}; use crate::models::state::wallet::WalletSecret; @@ -29,7 +30,6 @@ use rand::Rng; use rand::SeedableRng; use std::ops::Deref; use std::time::Duration; -use std::time::{SystemTime, UNIX_EPOCH}; use tasm_lib::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator; use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; use tokio::select; @@ -51,7 +51,7 @@ const MOCK_MAX_BLOCK_SIZE: u32 = 1_000_000; fn make_block_template( previous_block: &Block, transaction: Transaction, - timestamp: Duration, + mut block_timestamp: Timestamp, ) -> (BlockHeader, BlockBody) { let additions = transaction.kernel.outputs.clone(); let removals = transaction.kernel.inputs.clone(); @@ -80,10 +80,9 @@ fn make_block_template( let new_pow_line: U32s<5> = previous_block.kernel.header.proof_of_work_family + previous_block.kernel.header.difficulty; let next_block_height = previous_block.kernel.header.height.next(); - let mut block_timestamp = timestamp.as_millis() as u64; - if block_timestamp < previous_block.kernel.header.timestamp.value() { + if block_timestamp < previous_block.kernel.header.timestamp { warn!("Received block is timestamped in the future; mining on future-timestamped block."); - block_timestamp = previous_block.kernel.header.timestamp.value() + 1; + block_timestamp = previous_block.kernel.header.timestamp + Timestamp::seconds(1); } let difficulty: U32s<5> = Block::difficulty_control(previous_block, block_timestamp); @@ -91,7 +90,7 @@ fn make_block_template( version: zero, height: next_block_height, prev_block_digest: previous_block.kernel.mast_hash(), - timestamp: BFieldElement::new(block_timestamp), + timestamp: block_timestamp, nonce: [zero, zero, zero], max_block_size: MOCK_MAX_BLOCK_SIZE, proof_of_work_line: new_pow_line, @@ -179,7 +178,7 @@ fn make_coinbase_transaction( wallet_secret: &WalletSecret, block_height: BlockHeight, mutator_set_accumulator: MutatorSetAccumulator, - timestamp: Duration, + timestamp: Timestamp, ) -> (Transaction, Digest) { let sender_randomness: Digest = wallet_secret.generate_sender_randomness(block_height, receiver_digest); @@ -192,7 +191,7 @@ fn make_coinbase_transaction( *NeptuneCoins::decode(&coin.state) .expect("Make coinbase transaction: failed to parse coin state as amount.") }) - .sum(); + .sum::(); let coinbase_addition_record = commit( Hash::hash(coinbase_utxo), sender_randomness, @@ -204,8 +203,8 @@ fn make_coinbase_transaction( outputs: vec![coinbase_addition_record], public_announcements: vec![], fee: NeptuneCoins::zero(), - timestamp: BFieldElement::new(timestamp.as_millis() as u64), coinbase: Some(coinbase_amount), + timestamp, mutator_set_hash: mutator_set_accumulator.hash(), }; @@ -235,7 +234,7 @@ fn make_coinbase_transaction( fn create_block_transaction( latest_block: &Block, global_state: &GlobalState, - timestamp: Duration, + timestamp: Timestamp, ) -> (Transaction, ExpectedUtxo) { let block_capacity_for_transactions = SIZE_20MB_IN_BYTES; @@ -324,7 +323,7 @@ pub async fn mine( None } else { // Build the block template and spawn the worker thread to mine on it - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let now = Timestamp::now(); let (transaction, coinbase_utxo_info) = create_block_transaction( &latest_block, global_state_lock.lock_guard().await.deref(), @@ -414,7 +413,7 @@ pub async fn mine( // The block, however, *must* be valid on other parameters. So here, we should panic // if it is not. - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let now = Timestamp::now(); assert!(new_block_info.block.is_valid(&latest_block, now), "Own mined block must be valid. Failed validity check after successful PoW check."); info!("Found new {} block with block height {}. Hash: {}", global_state_lock.cli().network, new_block_info.block.kernel.header.height, new_block_info.block.hash().emojihash()); @@ -449,7 +448,8 @@ mod mine_loop_tests { use tracing_test::traced_test; use crate::{ - config_models::network::Network, models::state::UtxoReceiverData, + config_models::network::Network, + models::{consensus::timestamp::Timestamp, state::UtxoReceiverData}, tests::shared::get_mock_global_state, }; @@ -471,7 +471,7 @@ mod mine_loop_tests { // Verify constructed coinbase transaction and block template when mempool is empty let genesis_block = Block::genesis_block(network); - let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + let now = genesis_block.kernel.header.timestamp; let (transaction_empty_mempool, _coinbase_sender_randomness) = create_block_transaction(&genesis_block, &premine_receiver_global_state, now); assert_eq!( @@ -515,7 +515,7 @@ mod mine_loop_tests { }), ], NeptuneCoins::new(1), - now + Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000), + now + Timestamp::months(7), ) .await .unwrap(); @@ -529,7 +529,7 @@ mod mine_loop_tests { create_block_transaction( &genesis_block, &premine_receiver_global_state, - now + Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000 + 1000), + now + Timestamp::months(7), ); assert_eq!( 3, @@ -542,13 +542,13 @@ mod mine_loop_tests { let (block_header_template, block_body) = make_block_template( &genesis_block, transaction_non_empty_mempool, - now + Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000 + 2000), + now + Timestamp::months(7), ); let block_template_non_empty_mempool = Block::new(block_header_template, block_body, None); assert!( block_template_non_empty_mempool.is_valid( &genesis_block, - now + Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000 + 2000) + now + Timestamp::months(7) + Timestamp::seconds(2) ), "Block template created by miner with non-empty mempool must be valid" ); diff --git a/src/models/blockchain/block/block_header.rs b/src/models/blockchain/block/block_header.rs index d766277b..ae5342d3 100644 --- a/src/models/blockchain/block/block_header.rs +++ b/src/models/blockchain/block/block_header.rs @@ -1,3 +1,4 @@ +use crate::models::consensus::timestamp::Timestamp; use crate::prelude::twenty_first; use crate::models::consensus::mast_hash::HasDiscriminant; @@ -26,7 +27,7 @@ pub struct BlockHeader { // TODO: Reject blocks that are more than 10 seconds into the future // number of milliseconds since unix epoch - pub timestamp: BFieldElement, + pub timestamp: Timestamp, // TODO: Consider making a type for `nonce` pub nonce: [BFieldElement; 3], diff --git a/src/models/blockchain/block/mod.rs b/src/models/blockchain/block/mod.rs index 8a81645b..75453c9e 100644 --- a/src/models/blockchain/block/mod.rs +++ b/src/models/blockchain/block/mod.rs @@ -1,5 +1,6 @@ use crate::config_models::network::Network; use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::timestamp::Timestamp; use crate::models::consensus::{ValidityAstType, ValidityTree, WitnessType}; use crate::prelude::twenty_first; @@ -10,7 +11,6 @@ use num_traits::{abs, Zero}; use serde::{Deserialize, Serialize}; use std::cmp::max; -use std::time::Duration; use tasm_lib::triton_vm::proof::Proof; use tasm_lib::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator; use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; @@ -185,6 +185,7 @@ impl Block { height: BFieldElement::zero().into(), prev_block_digest: Digest::default(), timestamp: network.launch_date(), + // to be set to something difficult to predict ahead of time nonce: [ BFieldElement::zero(), BFieldElement::zero(), @@ -221,9 +222,9 @@ impl Block { for (receiving_address, amount) in Self::premine_distribution(network) { // generate utxo let mut utxo = Utxo::new_native_coin(receiving_address.lock_script(), amount); - let six_months = 365 * 24 * 60 * 60 * 1000 / 2; + let six_months = Timestamp::months(6); utxo.coins - .push(TimeLock::until(network.launch_date().value() + six_months)); + .push(TimeLock::until(network.launch_date() + six_months)); utxos.push(utxo); } utxos @@ -244,13 +245,13 @@ impl Block { previous_mutator_set_accumulator: &MutatorSetAccumulator, ) { // merge transactions - let merged_timestamp = BFieldElement::new(max( - self.kernel.header.timestamp.value(), - max( - self.kernel.body.transaction.kernel.timestamp.value(), - transaction.kernel.timestamp.value(), + let merged_timestamp = max::( + self.kernel.header.timestamp, + max::( + self.kernel.body.transaction.kernel.timestamp, + transaction.kernel.timestamp, ), - )); + ); let new_transaction = self .kernel .body @@ -298,7 +299,7 @@ impl Block { /// Verify a block. It is assumed that `previous_block` is valid. /// Note that this function does **not** check that the PoW digest is below the threshold. /// That must be done separately by the caller. - pub(crate) fn is_valid(&self, previous_block: &Block, now: Duration) -> bool { + pub(crate) fn is_valid(&self, previous_block: &Block, now: Timestamp) -> bool { // The block value doesn't actually change. Some function calls just require // mutable references because that's how the interface was defined for them. let block_copy = self.to_owned(); @@ -347,29 +348,27 @@ impl Block { } // 0.d) Block timestamp is greater than (or equal to) that of previous block - if previous_block.kernel.header.timestamp.value() - > block_copy.kernel.header.timestamp.value() - { + if previous_block.kernel.header.timestamp > block_copy.kernel.header.timestamp { warn!( "Block's timestamp ({}) should be greater than or equal to that of previous block ({})\nprevious <= current ?? {}", - block_copy.kernel.header.timestamp.value(), - previous_block.kernel.header.timestamp.value(), - previous_block.kernel.header.timestamp.value() <= block_copy.kernel.header.timestamp.value() + block_copy.kernel.header.timestamp, + previous_block.kernel.header.timestamp, + previous_block.kernel.header.timestamp <= block_copy.kernel.header.timestamp ); return false; } // 0.e) Target difficulty, and other control parameters, were updated correctly if block_copy.kernel.header.difficulty - != Self::difficulty_control(previous_block, block_copy.kernel.header.timestamp.value()) + != Self::difficulty_control(previous_block, block_copy.kernel.header.timestamp) { warn!("Value for new difficulty is incorrect."); return false; } // 0.f) Block timestamp is less than host-time (utc) + 2 hours. - let future_limit = now + Duration::from_secs(60 * 60 * 2); - if (block_copy.kernel.header.timestamp.value() as u128) >= future_limit.as_millis() { + let future_limit = now + Timestamp::hours(2); + if block_copy.kernel.header.timestamp >= future_limit { warn!("block time is too far in the future"); return false; } @@ -436,13 +435,12 @@ impl Block { } // 1.e) verify that the transaction timestamp is less than or equal to the block's timestamp. - if block_copy.kernel.body.transaction.kernel.timestamp.value() - > block_copy.kernel.header.timestamp.value() + if block_copy.kernel.body.transaction.kernel.timestamp > block_copy.kernel.header.timestamp { warn!( "Transaction timestamp ({}) is is larger than that of block ({})", - block_copy.kernel.body.transaction.kernel.timestamp.value(), - block_copy.kernel.header.timestamp.value() + block_copy.kernel.body.transaction.kernel.timestamp, + block_copy.kernel.header.timestamp ); return false; } @@ -518,7 +516,7 @@ impl Block { /// We assume that the block timestamp is valid. pub fn difficulty_control( old_block: &Block, - new_timestamp: u64, + new_timestamp: Timestamp, ) -> U32s { // no adjustment if the previous block is the genesis block if old_block.kernel.header.height.is_genesis() { @@ -526,9 +524,9 @@ impl Block { } // otherwise, compute PID control signal - let t = new_timestamp - old_block.kernel.header.timestamp.value(); + let t = new_timestamp - old_block.kernel.header.timestamp; - let new_error = t as i64 - TARGET_BLOCK_INTERVAL as i64; + let new_error = t.0.value() as i64 - TARGET_BLOCK_INTERVAL as i64; let adjustment = -new_error / 100; let absolute_adjustment = abs(adjustment) as u64; @@ -596,8 +594,8 @@ mod block_tests { let genesis_block = Block::genesis_block(network); let (mut block_1, _, _) = make_mock_block(&genesis_block, None, address, rng.gen()); - let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let now = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); assert!( block_1.is_valid(&genesis_block, now), "Block 1 must be valid with only coinbase output" @@ -694,7 +692,7 @@ mod block_tests { make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); block_1.kernel.body.block_mmr_accumulator = MmrAccumulator::new(vec![]); - let timestamp = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + let timestamp = genesis_block.kernel.header.timestamp; assert!(!block_1.is_valid(&genesis_block, timestamp)); } @@ -705,7 +703,7 @@ mod block_tests { let mut rng = thread_rng(); let network = Network::RegTest; let genesis_block = Block::genesis_block(network); - let mut now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + let mut now = genesis_block.kernel.header.timestamp; let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); @@ -713,29 +711,25 @@ mod block_tests { make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); // Set block timestamp 1 hour in the future. (is valid) - let future_time1 = now + Duration::from_secs(60 * 60); - block_1.kernel.header.timestamp = - BFieldElement::new(future_time1.as_millis().try_into().unwrap()); + let future_time1 = now + Timestamp::hours(1); + block_1.kernel.header.timestamp = future_time1; assert!(block_1.is_valid(&genesis_block, now)); - now = Duration::from_millis(block_1.kernel.header.timestamp.value()); + now = block_1.kernel.header.timestamp; // Set block timestamp 2 hours - 1 sec in the future. (is valid) - let future_time2 = now + Duration::from_secs(60 * 60 * 2 - 1); - block_1.kernel.header.timestamp = - BFieldElement::new(future_time2.as_millis().try_into().unwrap()); + let future_time2 = now + Timestamp::hours(2) - Timestamp::seconds(1); + block_1.kernel.header.timestamp = future_time2; assert!(block_1.is_valid(&genesis_block, now)); // Set block timestamp 2 hours + 10 secs in the future. (not valid) - let future_time3 = now + Duration::from_secs(60 * 60 * 2 + 10); - block_1.kernel.header.timestamp = - BFieldElement::new(future_time3.as_millis().try_into().unwrap()); + let future_time3 = now + Timestamp::hours(2) + Timestamp::seconds(10); + block_1.kernel.header.timestamp = future_time3; assert!(!block_1.is_valid(&genesis_block, now)); // Set block timestamp 2 days in the future. (not valid) - let future_time4 = now + Duration::from_secs(86400 * 2); - block_1.kernel.header.timestamp = - BFieldElement::new(future_time4.as_millis().try_into().unwrap()); + let future_time4 = now + Timestamp::seconds(86400 * 2); + block_1.kernel.header.timestamp = future_time4; assert!(!block_1.is_valid(&genesis_block, now)); } diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index 2febdc51..37c1ce29 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -18,7 +18,6 @@ use serde::{Deserialize, Serialize}; use std::cmp::max; use std::collections::HashMap; use std::hash::{Hash as StdHash, Hasher as StdHasher}; -use std::time::SystemTime; use tasm_lib::Digest; use tracing::{debug, error, warn}; use triton_vm::prelude::NonDeterminism; @@ -244,10 +243,6 @@ impl Transaction { } } - pub fn get_timestamp(&self) -> Result { - Ok(std::time::UNIX_EPOCH + std::time::Duration::from_millis(self.kernel.timestamp.value())) - } - /// Determine whether the transaction is valid (forget about confirmable). /// This method tests the transaction's internal consistency in isolation, /// without the context of the canonical chain. @@ -303,10 +298,7 @@ impl Transaction { self.kernel.mutator_set_hash, other.kernel.mutator_set_hash, "Mutator sets must be equal for transaction merger." ); - let timestamp = BFieldElement::new(max( - self.kernel.timestamp.value(), - other.kernel.timestamp.value(), - )); + let timestamp = max(self.kernel.timestamp, other.kernel.timestamp); let merged_coinbase = match self.kernel.coinbase { Some(_) => match other.kernel.coinbase { @@ -612,7 +604,7 @@ mod witness_tests { public_announcements: vec![], fee: NeptuneCoins::new(0), coinbase: None, - timestamp: BFieldElement::new(0), + timestamp: Default::default(), mutator_set_hash: Digest::default(), }; let primitive_witness = PrimitiveWitness { @@ -635,14 +627,16 @@ mod witness_tests { #[cfg(test)] mod transaction_tests { use rand::random; - use std::time::Duration; use tracing_test::traced_test; use transaction_tests::utxo::{LockScript, Utxo}; use super::*; use crate::{ - models::blockchain::type_scripts::neptune_coins::NeptuneCoins, - tests::shared::make_mock_transaction, util_types::mutator_set::mutator_set_trait::commit, + models::{ + blockchain::type_scripts::neptune_coins::NeptuneCoins, consensus::timestamp::Timestamp, + }, + tests::shared::make_mock_transaction, + util_types::mutator_set::mutator_set_trait::commit, }; #[traced_test] @@ -657,12 +651,7 @@ mod transaction_tests { // Verify that a sane timestamp is returned. `make_mock_transaction` must follow // the correct time convention for this test to work. let coinbase_transaction = make_mock_transaction(vec![], vec![ar]); - assert!( - SystemTime::now() - .duration_since(coinbase_transaction.get_timestamp().unwrap()) - .unwrap() - < Duration::from_secs(10) - ); + assert!(Timestamp::now() - coinbase_transaction.kernel.timestamp < Timestamp::seconds(10)); } #[test] diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 664f9fb1..bfb4c05e 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -1,5 +1,3 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - use get_size::GetSize; use itertools::Itertools; use num_traits::CheckedSub; @@ -22,7 +20,7 @@ use tasm_lib::{ use crate::models::{ blockchain::type_scripts::{native_currency::NativeCurrency, neptune_coins::NeptuneCoins}, - consensus::tasm::program::ConsensusProgram, + consensus::{tasm::program::ConsensusProgram, timestamp::Timestamp}, }; use crate::{ models::{blockchain::type_scripts::TypeScript, state::wallet::address::generation_address}, @@ -361,12 +359,7 @@ pub(crate) fn arbitrary_primitive_witness_with( public_announcements: public_announcements.to_vec(), fee, coinbase, - timestamp: BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64, - ), + timestamp: Timestamp::now(), mutator_set_hash: mutator_set_accumulator.hash(), }; diff --git a/src/models/blockchain/transaction/transaction_kernel.rs b/src/models/blockchain/transaction/transaction_kernel.rs index 025128ed..b02d46d0 100644 --- a/src/models/blockchain/transaction/transaction_kernel.rs +++ b/src/models/blockchain/transaction/transaction_kernel.rs @@ -1,9 +1,10 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - use crate::{ models::{ blockchain::type_scripts::neptune_coins::{pseudorandom_amount, NeptuneCoins}, - consensus::mast_hash::{HasDiscriminant, MastHash}, + consensus::{ + mast_hash::{HasDiscriminant, MastHash}, + timestamp::Timestamp, + }, }, prelude::twenty_first, }; @@ -44,7 +45,7 @@ pub struct TransactionKernel { pub coinbase: Option, // number of milliseconds since unix epoch - pub timestamp: BFieldElement, + pub timestamp: Timestamp, pub mutator_set_hash: Digest, } @@ -130,7 +131,7 @@ pub fn pseudorandom_transaction_kernel( .collect_vec(); let fee = pseudorandom_amount(rng.gen::<[u8; 32]>()); let coinbase = pseudorandom_option(rng.gen(), pseudorandom_amount(rng.gen::<[u8; 32]>())); - let timestamp: BFieldElement = rng.gen(); + let timestamp: Timestamp = rng.gen(); let mutator_set_hash: Digest = rng.gen(); TransactionKernel { @@ -160,12 +161,7 @@ impl<'a> Arbitrary<'a> for TransactionKernel { .collect_vec(); let fee: NeptuneCoins = u.arbitrary()?; let coinbase: Option = u.arbitrary()?; - let timestamp: BFieldElement = BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64, - ); + let timestamp = Timestamp::now(); let mutator_set_hash: Digest = u.arbitrary()?; let transaction_kernel = TransactionKernel { diff --git a/src/models/blockchain/transaction/utxo.rs b/src/models/blockchain/transaction/utxo.rs index 72e8ddc5..3ee6f02f 100644 --- a/src/models/blockchain/transaction/utxo.rs +++ b/src/models/blockchain/transaction/utxo.rs @@ -1,6 +1,7 @@ use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::blockchain::type_scripts::time_lock::TimeLock; use crate::models::consensus::tasm::program::ConsensusProgram; +use crate::models::consensus::timestamp::Timestamp; use crate::prelude::{triton_vm, twenty_first}; use crate::models::blockchain::shared::Hash; @@ -12,7 +13,6 @@ use rand::rngs::StdRng; use rand::{Rng, RngCore, SeedableRng}; use serde::{Deserialize, Serialize}; use std::hash::{Hash as StdHash, Hasher as StdHasher}; -use std::time::Duration; use triton_vm::instruction::LabelledInstruction; use triton_vm::program::Program; use triton_vm::triton_asm; @@ -89,12 +89,13 @@ impl Utxo { } /// If the UTXO has a timelock, find out what the release date is. - pub fn release_date(&self) -> Option { + pub fn release_date(&self) -> Option { self.coins .iter() .find(|coin| coin.type_script_hash == TimeLock.hash()) .map(|coin| coin.state[0].value()) - .map(Duration::from_millis) + .map(BFieldElement::new) + .map(Timestamp) } /// Determine whether the UTXO has coins that contain only known type @@ -116,7 +117,7 @@ impl Utxo { /// assuming it can be unlocked. Currently, this boils down to checking /// whether it has a time lock and if it does, verifying that the release /// date is in the past. - pub fn can_spend_at(&self, timestamp: u64) -> bool { + pub fn can_spend_at(&self, timestamp: Timestamp) -> bool { // unknown type script if !self.has_known_type_scripts() { return false; @@ -129,9 +130,9 @@ impl Utxo { .filter(|c| c.type_script_hash == TimeLock.hash()) .map(|c| c.state.clone()) { - match BFieldElement::decode(&state) { + match Timestamp::decode(&state) { Ok(release_date) => { - if timestamp <= release_date.value() { + if timestamp <= *release_date { return false; } } @@ -146,7 +147,7 @@ impl Utxo { /// Determine whether the only thing preventing the UTXO from being spendable /// is the timelock whose according release date is in the future. - pub fn is_timelocked_but_otherwise_spendable_at(&self, timestamp: u64) -> bool { + pub fn is_timelocked_but_otherwise_spendable_at(&self, timestamp: Timestamp) -> bool { if !self.has_known_type_scripts() { return false; } @@ -159,9 +160,9 @@ impl Utxo { .filter(|c| c.type_script_hash == TimeLock.hash()) .map(|c| c.state.clone()) { - match BFieldElement::decode(&state) { + match Timestamp::decode(&state) { Ok(release_date) => { - if timestamp <= release_date.value() { + if timestamp <= *release_date { have_future_release_date = true; } } @@ -306,10 +307,10 @@ mod utxo_tests { #[test] fn utxo_timelock_test() { let mut rng = thread_rng(); - let release_date = rng.next_u64() >> 1; - let mut delta = release_date + 1; + let release_date = rng.gen::(); + let mut delta = release_date + Timestamp::seconds(1); while delta > release_date { - delta = rng.next_u64() >> 1; + delta = Timestamp(BFieldElement::new(rng.next_u64() >> 1)); } let mut utxo = Utxo::new( LockScript { diff --git a/src/models/blockchain/transaction/validity.rs b/src/models/blockchain/transaction/validity.rs index a9541cfd..6928517f 100644 --- a/src/models/blockchain/transaction/validity.rs +++ b/src/models/blockchain/transaction/validity.rs @@ -1,6 +1,7 @@ use crate::models::blockchain::block::mutator_set_update::MutatorSetUpdate; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::timestamp::Timestamp; use crate::models::consensus::{ ValidationLogic, ValidityAstType, ValidityTree, WhichProgram, WitnessType, }; @@ -89,8 +90,7 @@ impl TransactionValidationLogic { || own_kernel.coinbase.unwrap_or(NeptuneCoins::new(0)) + other_kernel.coinbase.unwrap_or(NeptuneCoins::new(0)) != new_kernel.coinbase.unwrap_or(NeptuneCoins::new(0)) - || u64::max(own_kernel.timestamp.value(), other_kernel.timestamp.value()) - != new_kernel.timestamp.value() + || Timestamp::max(own_kernel.timestamp, other_kernel.timestamp) != new_kernel.timestamp || own_kernel.mutator_set_hash != other_kernel.mutator_set_hash || other_kernel.mutator_set_hash != new_kernel.mutator_set_hash { diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index b3a20801..a51eb4cd 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -9,6 +9,7 @@ use crate::models::blockchain::transaction::transaction_kernel::TransactionKerne use crate::models::blockchain::transaction::utxo::Coin; use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::timestamp::Timestamp; use crate::models::consensus::SecretWitness; use crate::models::consensus::ValidationLogic; use crate::models::consensus::ValidityAstType; @@ -50,10 +51,10 @@ impl TimeLock { /// Create a `TimeLock` type-script-and-state-pair that releases the coins at the /// given release date, which corresponds to the number of milliseconds that passed /// since the unix epoch started (00:00 am UTC on Jan 1 1970). - pub fn until(date: u64) -> Coin { + pub fn until(date: Timestamp) -> Coin { Coin { type_script_hash: TimeLock.hash(), - state: vec![BFieldElement::new(date)], + state: vec![date.0], } } } @@ -410,7 +411,7 @@ impl SecretWitness for TimeLockWitness { SaltedUtxos::empty(), ); let individual_tokens = vec![ - self.transaction_kernel.timestamp, + self.transaction_kernel.timestamp.0, input_salted_utxos_address, output_salted_utxos_address, ]; @@ -496,7 +497,7 @@ impl Arbitrary for TimeLockWitness { /// coin is absent. /// - num_outputs : usize Number of outputs. /// - num_public_announcements : usize Number of public announcements. - type Parameters = (Vec, usize, usize); + type Parameters = (Vec, usize, usize); type Strategy = BoxedStrategy; @@ -532,7 +533,7 @@ impl Arbitrary for TimeLockWitness { // add time locks to input UTXOs for (utxo, release_date) in input_utxos.iter_mut().zip(release_dates.iter()) { - if *release_date != 0 { + if !release_date.is_zero() { let time_lock_coin = TimeLock::until(*release_date); utxo.coins.push(time_lock_coin); } @@ -575,17 +576,17 @@ impl Arbitrary for TimeLockWitness { #[cfg(test)] mod test { - use std::time::{SystemTime, UNIX_EPOCH}; - use proptest::{collection::vec, strategy::Just}; + use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; use test_strategy::proptest; use crate::models::{ blockchain::type_scripts::time_lock::TimeLock, - consensus::{tasm::program::ConsensusProgram, SecretWitness}, + consensus::{tasm::program::ConsensusProgram, timestamp::Timestamp, SecretWitness}, }; use super::TimeLockWitness; + use itertools::Itertools; #[proptest] fn test_unlocked( @@ -593,7 +594,7 @@ mod test { #[strategy(1usize..=3)] _num_outputs: usize, #[strategy(1usize..=3)] _num_public_announcements: usize, #[strategy(vec(Just(0u64), #_num_inputs))] _release_dates: Vec, - #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates.iter().cloned().map(BFieldElement::new).map(Timestamp).collect_vec(), #_num_outputs, #_num_public_announcements)))] time_lock_witness: TimeLockWitness, ) { assert!( @@ -607,24 +608,17 @@ mod test { ); } - fn now() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64 - } - #[proptest] fn test_locked( #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, #[strategy(1usize..=3)] _num_public_announcements: usize, - #[strategy(vec(now()+1000*60*60*24..now()+1000*60*60*24*7, #_num_inputs))] + #[strategy(vec(Timestamp::now().0.value()+Timestamp::days(1).0.value()..Timestamp::now().0.value()+Timestamp::days(7).0.value(), #_num_inputs))] _release_dates: Vec, - #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates.iter().cloned().map(BFieldElement::new).map(Timestamp).collect_vec(), #_num_outputs, #_num_public_announcements)))] time_lock_witness: TimeLockWitness, ) { - println!("now: {}", now()); + println!("now: {}", Timestamp::now()); assert!( TimeLock {} .run( @@ -641,12 +635,12 @@ mod test { #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, #[strategy(1usize..=3)] _num_public_announcements: usize, - #[strategy(vec(now()-1000*60*60*24*7..now()-1000*60*60*24, #_num_inputs))] + #[strategy(vec(Timestamp::now().0.value()-Timestamp::days(7).0.value()..Timestamp::now().0.value()-Timestamp::days(1).0.value(), #_num_inputs))] _release_dates: Vec, - #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates.iter().cloned().map(BFieldElement::new).map(Timestamp).collect_vec(), #_num_outputs, #_num_public_announcements)))] time_lock_witness: TimeLockWitness, ) { - println!("now: {}", now()); + println!("now: {}", Timestamp::now()); assert!( TimeLock .run( diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index 1e0ed3ff..be79baa4 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -24,6 +24,7 @@ use self::tasm::program::ConsensusProgram; pub mod mast_hash; pub mod tasm; +pub mod timestamp; /// The claim to validiy of a block or transaction (a *validity claim*) is a Boolean /// expression for which we build an abstract syntax tree. Nodes in this tree assume diff --git a/src/models/consensus/timestamp.rs b/src/models/consensus/timestamp.rs new file mode 100644 index 00000000..25954169 --- /dev/null +++ b/src/models/consensus/timestamp.rs @@ -0,0 +1,141 @@ +use std::{ + fmt::Display, + ops::{Add, Sub}, + time::{SystemTime, UNIX_EPOCH}, +}; + +use arbitrary::Arbitrary; +use chrono::{DateTime, Local, NaiveDateTime, Utc}; +use get_size::GetSize; +use num_traits::Zero; +use rand::distributions::{Distribution, Standard}; +use serde::{Deserialize, Serialize}; +use tasm_lib::twenty_first::shared_math::{ + b_field_element::BFieldElement, bfield_codec::BFieldCodec, +}; + +/// Dedicated struct for timestamps (and durations). Counts the number of +/// milliseconds elapsed since the Unix epoch (00:00 UTC on 1 Jan 1970) using +/// a single BFieldElement. +#[derive( + Debug, Clone, Copy, BFieldCodec, PartialEq, Eq, Serialize, Deserialize, GetSize, Default, +)] +pub struct Timestamp(pub BFieldElement); + +impl PartialOrd for Timestamp { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Timestamp { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.value().cmp(&other.0.value()) + } +} + +impl Zero for Timestamp { + fn zero() -> Self { + Timestamp(BFieldElement::new(0)) + } + + fn is_zero(&self) -> bool { + self.0 == BFieldElement::new(0) + } +} + +impl Add for Timestamp { + type Output = Timestamp; + + fn add(self, rhs: Self) -> Self::Output { + Timestamp(self.0 + rhs.0) + } +} + +impl Sub for Timestamp { + type Output = Timestamp; + + fn sub(self, rhs: Self) -> Self::Output { + Timestamp(self.0 - rhs.0) + } +} + +impl Timestamp { + pub fn now() -> Timestamp { + Timestamp(BFieldElement::new( + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64, + )) + } + + pub fn months(num: usize) -> Timestamp { + Timestamp(BFieldElement::new((num as u64) * 365240 * 2 * 60 * 60)) + } + + pub fn days(num: usize) -> Timestamp { + Timestamp(BFieldElement::new((num as u64) * 24 * 60 * 60 * 1000)) + } + + pub fn hours(num: usize) -> Timestamp { + Timestamp(BFieldElement::new((num as u64) * 60 * 60 * 1000)) + } + + pub fn minutes(num: usize) -> Timestamp { + Timestamp(BFieldElement::new((num as u64) * 60 * 1000)) + } + + pub fn seconds(num: u64) -> Timestamp { + Timestamp(BFieldElement::new(num * 1000)) + } + + pub fn millis(num: u64) -> Timestamp { + Timestamp(BFieldElement::new(num)) + } + + pub fn format(&self, format_descriptor: &str) -> String { + match DateTime::from_timestamp_millis(self.0.value() as i64) { + Some(dt) => dt.format(format_descriptor).to_string(), + None => "".to_string(), + } + } + + pub fn standard_format(&self) -> String { + let naive = + NaiveDateTime::from_timestamp_millis(self.0.value().try_into().unwrap_or(0)).unwrap(); + let utc: DateTime = DateTime::from_naive_utc_and_offset(naive, *Utc::now().offset()); + let offset: DateTime = DateTime::from(utc); + offset.to_rfc2822() + } +} + +impl Display for Timestamp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0.value()) + } +} + +#[cfg(test)] +mod test { + use crate::models::consensus::timestamp::Timestamp; + + #[test] + fn print_now() { + println!("{}", Timestamp::now()); + } +} + +impl Distribution for Standard { + fn sample(&self, rng: &mut R) -> Timestamp { + Timestamp(rng.gen::()) + } +} + +impl<'a> Arbitrary<'a> for Timestamp { + fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { + Result::Ok(Timestamp(BFieldElement::new( + (u.arbitrary::()? % (BFieldElement::P as u128)) as u64, + ))) + } +} diff --git a/src/models/database.rs b/src/models/database.rs index 04403dba..a0e4ada6 100644 --- a/src/models/database.rs +++ b/src/models/database.rs @@ -2,11 +2,11 @@ use crate::prelude::twenty_first; use serde::{Deserialize, Serialize}; use std::{fmt, net::IpAddr}; -use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::digest::Digest; use super::blockchain::block::block_header::BlockHeader; use super::blockchain::block::block_height::BlockHeight; +use super::consensus::timestamp::Timestamp; use super::peer::PeerStanding; use crate::database::NeptuneLevelDb; @@ -35,8 +35,8 @@ pub struct FileRecord { pub max_block_height: BlockHeight, // min and max block timestamp in file, both inclusive - pub min_block_timestamp: BFieldElement, - pub max_block_timestamp: BFieldElement, + pub min_block_timestamp: Timestamp, + pub max_block_timestamp: Timestamp, } impl FileRecord { @@ -59,16 +59,14 @@ impl FileRecord { ret.file_size += block_size; ret.min_block_height = std::cmp::min(self.max_block_height, block_header.height); ret.max_block_height = std::cmp::max(self.max_block_height, block_header.height); - ret.min_block_timestamp = std::cmp::min_by( - ret.min_block_timestamp, - block_header.timestamp, - |x: &BFieldElement, y: &BFieldElement| x.value().cmp(&y.value()), - ); - ret.max_block_timestamp = std::cmp::max_by( - ret.min_block_timestamp, - block_header.timestamp, - |x: &BFieldElement, y: &BFieldElement| x.value().cmp(&y.value()), - ); + ret.min_block_timestamp = + std::cmp::min_by(ret.min_block_timestamp, block_header.timestamp, |x, y| { + x.cmp(y) + }); + ret.max_block_timestamp = + std::cmp::max_by(ret.min_block_timestamp, block_header.timestamp, |x, y| { + x.cmp(y) + }); ret } } diff --git a/src/models/state/archival_state.rs b/src/models/state/archival_state.rs index 7f3e8b6e..ac52036b 100644 --- a/src/models/state/archival_state.rs +++ b/src/models/state/archival_state.rs @@ -721,10 +721,11 @@ impl ArchivalState { debug!( "Updating mutator set: adding block with height {}. Mined: {}", apply_forward_block.kernel.header.height, - crate::utc_timestamp_to_localtime( - apply_forward_block.kernel.header.timestamp.value() - ) - .to_string() + apply_forward_block + .kernel + .header + .timestamp + .standard_format() ); let mut addition_records: Vec = apply_forward_block @@ -790,8 +791,6 @@ impl ArchivalState { #[cfg(test)] mod archival_state_tests { - use std::time::Duration; - use super::*; use crate::config_models::network::Network; @@ -800,6 +799,7 @@ mod archival_state_tests { use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::consensus::mast_hash::MastHash; + use crate::models::consensus::timestamp::Timestamp; use crate::models::state::archival_state::ArchivalState; use crate::models::state::global_state_tests::create_transaction_with_timestamp; use crate::models::state::wallet::utxo_notification_pool::UtxoNotifier; @@ -982,8 +982,8 @@ mod archival_state_tests { assert_ne!(0, ams_ref.ams().kernel.aocl.count_leaves()); } - let now = Duration::from_millis(mock_block_1.kernel.header.timestamp.value()); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let now = mock_block_1.kernel.header.timestamp; + let seven_months = Timestamp::months(7); // Add an input to the next block's transaction. This will add a removal record // to the block, and this removal record will insert indices in the Bloom filter. @@ -1114,8 +1114,8 @@ mod archival_state_tests { rng.gen(), ); let genesis_block = archival_state.genesis_block.clone(); - let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let now = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let one_money = NeptuneCoins::new(42).to_native_coins(); let receiver_data = vec![ @@ -1249,8 +1249,8 @@ mod archival_state_tests { own_receiving_address, rng.gen(), ); - let now = Duration::from_millis(next_block.kernel.header.timestamp.value()); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let now = next_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let receiver_data = vec![ UtxoReceiverData { utxo: Utxo { @@ -1402,8 +1402,8 @@ mod archival_state_tests { let genesis_wallet = genesis_wallet_state.wallet_secret; let own_receiving_address = genesis_wallet.nth_generation_spending_key(0).to_address(); let genesis_block = Block::genesis_block(network); - let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let now = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let (mut block_1_a, _, _) = make_mock_block_with_valid_pow(&genesis_block, None, own_receiving_address, rng.gen()); let global_state_lock = get_mock_global_state(network, 42, genesis_wallet).await; @@ -1464,8 +1464,8 @@ mod archival_state_tests { let bob_state_lock = get_mock_global_state(network, 3, wallet_secret_bob).await; let genesis_block = Block::genesis_block(network); - let launch = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let launch = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let (mut block_1, cb_utxo, cb_output_randomness) = make_mock_block_with_valid_pow( &genesis_block, @@ -1527,7 +1527,7 @@ mod archival_state_tests { ] .concat(), fee, - (launch + seven_months).as_millis() as u64, + launch + seven_months, ) .await .unwrap(); @@ -1647,7 +1647,7 @@ mod archival_state_tests { .await .get_wallet_status_for_tip() .await - .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) + .synced_unspent_available_amount(launch + seven_months) ); assert_eq!( NeptuneCoins::new(200), @@ -1656,7 +1656,7 @@ mod archival_state_tests { .await .get_wallet_status_for_tip() .await - .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) + .synced_unspent_available_amount(launch + seven_months) ); // Make two transactions: Alice sends two UTXOs to Genesis and Bob sends three UTXOs to genesis @@ -1723,7 +1723,7 @@ mod archival_state_tests { &bob_state_lock, &receiver_data_from_bob.clone(), NeptuneCoins::new(2), - (launch + seven_months).as_millis() as u64, + launch + seven_months, ) .await .unwrap(); @@ -1747,7 +1747,7 @@ mod archival_state_tests { // Sanity checks assert_eq!(4, block_2.kernel.body.transaction.kernel.inputs.len()); assert_eq!(6, block_2.kernel.body.transaction.kernel.outputs.len()); - let now = Duration::from_millis(block_1.kernel.header.timestamp.value()); + let now = block_1.kernel.header.timestamp; assert!(block_2.is_valid(&block_1, now)); // Update chain states @@ -1789,14 +1789,14 @@ mod archival_state_tests { .await .get_wallet_status_for_tip() .await - .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) + .synced_unspent_available_amount(launch + seven_months) .is_zero()); assert!(bob_state_lock .lock_guard() .await .get_wallet_status_for_tip() .await - .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) + .synced_unspent_available_amount(launch + seven_months) .is_zero()); // Update genesis wallet and verify that all ingoing UTXOs are recorded diff --git a/src/models/state/mempool.rs b/src/models/state/mempool.rs index b65d4931..17f52480 100644 --- a/src/models/state/mempool.rs +++ b/src/models/state/mempool.rs @@ -9,7 +9,10 @@ //! density'. use crate::{ - models::{blockchain::type_scripts::neptune_coins::NeptuneCoins, consensus::WitnessType}, + models::{ + blockchain::type_scripts::neptune_coins::NeptuneCoins, + consensus::{timestamp::Timestamp, WitnessType}, + }, prelude::twenty_first, util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator, }; @@ -21,7 +24,6 @@ use priority_queue::{double_priority_queue::iterators::IntoSortedIter, DoublePri use std::{ collections::{hash_map::RandomState, HashMap, HashSet}, iter::Rev, - time::{Duration, SystemTime, UNIX_EPOCH}, }; use twenty_first::shared_math::digest::Digest; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; @@ -58,11 +60,6 @@ pub const TRANSACTION_NOTIFICATION_AGE_LIMIT_IN_SECS: u64 = 60 * 60 * 24; type LookupItem<'a> = (Digest, &'a Transaction); -/// Timestamp of 'now' encoded as the duration since epoch. -fn now() -> Duration { - SystemTime::now().duration_since(UNIX_EPOCH).unwrap() -} - #[derive(Debug, Clone, PartialEq, Eq, GetSize)] pub struct Mempool { max_total_size: usize, @@ -276,10 +273,10 @@ impl Mempool { /// Prune based on `Transaction.timestamp` /// Computes in O(n) pub fn prune_stale_transactions(&mut self) { - let cutoff = now() - Duration::from_secs(MEMPOOL_TX_THRESHOLD_AGE_IN_SECS); + let cutoff = Timestamp::now() - Timestamp::seconds(MEMPOOL_TX_THRESHOLD_AGE_IN_SECS); let keep = |(_transaction_id, transaction): LookupItem| -> bool { - cutoff.as_secs() < transaction.kernel.timestamp.value() + cutoff < transaction.kernel.timestamp }; self.retain(keep); @@ -416,11 +413,9 @@ mod tests { use num_bigint::BigInt; use num_traits::Zero; use rand::{random, rngs::StdRng, thread_rng, Rng, SeedableRng}; + use tasm_lib::twenty_first::util_types::emojihash_trait::Emojihash; use tracing::debug; use tracing_test::traced_test; - use twenty_first::{ - shared_math::b_field_element::BFieldElement, util_types::emojihash_trait::Emojihash, - }; #[tokio::test] pub async fn insert_then_get_then_remove_then_get() { @@ -511,8 +506,8 @@ mod tests { "Mempool must be empty after initialization" ); - let eight_days_ago = now() - Duration::from_secs(8 * 24 * 60 * 60); - let timestamp = Some(BFieldElement::new(eight_days_ago.as_secs())); + let eight_days_ago = Timestamp::now() - Timestamp::days(8); + let timestamp = Some(eight_days_ago); for i in 0u32..5 { let t = make_mock_transaction_with_wallet( @@ -632,8 +627,8 @@ mod tests { utxo: new_utxo, }); } - let mut now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let mut now = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let tx_by_preminer = premine_receiver_global_state .create_transaction( output_utxos_generated_by_me, @@ -722,7 +717,7 @@ mod tests { tx_by_other_updated.clone(), &block_2.kernel.body.mutator_set_accumulator, ); - now = Duration::from_millis(block_2.kernel.header.timestamp.value()); + now = block_2.kernel.header.timestamp; assert!( block_3_with_updated_tx.is_valid(&block_2, now + seven_months), "Block with tx with updated mutator set data must be valid" @@ -750,7 +745,7 @@ mod tests { tx_by_other_updated, &previous_block.kernel.body.mutator_set_accumulator, ); - now = Duration::from_millis(previous_block.kernel.header.timestamp.value()); + now = previous_block.kernel.header.timestamp; assert!( block_14.is_valid(&previous_block, now+seven_months), "Block with tx with updated mutator set data must be valid after 10 blocks have been mined" @@ -776,14 +771,8 @@ mod tests { let network = Network::RegTest; let preminer_state_lock = get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; - let now = Duration::from_millis( - Block::genesis_block(network) - .kernel - .header - .timestamp - .value(), - ); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let now = Block::genesis_block(network).kernel.header.timestamp; + let seven_months = Timestamp::months(7); let mut preminer_state = preminer_state_lock.lock_guard_mut().await; let premine_wallet_secret = &preminer_state.wallet_state.wallet_secret; let premine_spending_key = premine_wallet_secret.nth_generation_spending_key(0); diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index 34f5ca95..a2d4a36d 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -7,9 +7,7 @@ use itertools::Itertools; use num_traits::CheckedSub; use std::cmp::max; use std::ops::{Deref, DerefMut}; -use std::time::Duration; use tracing::{debug, info, warn}; -use twenty_first::shared_math::b_field_element::BFieldElement; use twenty_first::shared_math::bfield_codec::BFieldCodec; use twenty_first::shared_math::digest::Digest; use twenty_first::storage::storage_schema::traits::*; @@ -37,6 +35,7 @@ use super::blockchain::type_scripts::neptune_coins::NeptuneCoins; use super::blockchain::type_scripts::time_lock::TimeLock; use super::blockchain::type_scripts::TypeScript; use super::consensus::tasm::program::ConsensusProgram; +use super::consensus::timestamp::Timestamp; use crate::config_models::cli_args; use crate::models::peer::HandshakeData; use crate::models::state::wallet::monitored_utxo::MonitoredUtxo; @@ -343,7 +342,7 @@ impl GlobalState { } /// Retrieve wallet balance history - pub async fn get_balance_history(&self) -> Vec<(Digest, Duration, BlockHeight, NeptuneCoins)> { + pub async fn get_balance_history(&self) -> Vec<(Digest, Timestamp, BlockHeight, NeptuneCoins)> { let current_tip_digest = self.chain.light_state().hash(); let monitored_utxos = self.wallet_state.wallet_db.monitored_utxos(); @@ -384,7 +383,7 @@ impl GlobalState { pub async fn assemble_inputs_for_transaction( &mut self, total_spend: NeptuneCoins, - timestamp: u64, + timestamp: Timestamp, ) -> Result> { // Get the block tip as the transaction is made relative to it let block_tip = self.chain.light_state(); @@ -526,11 +525,11 @@ impl GlobalState { &mut self, receiver_data: Vec, fee: NeptuneCoins, - timestamp: Duration, + timestamp: Timestamp, ) -> Result { // UTXO data: inputs, outputs, and supporting witness data let (inputs, spendable_utxos_and_mps, outputs, output_utxos) = self - .generate_utxo_data_for_transaction(&receiver_data, fee, timestamp.as_millis() as u64) + .generate_utxo_data_for_transaction(&receiver_data, fee, timestamp) .await?; // other data @@ -562,7 +561,7 @@ impl GlobalState { output_utxos, fee, public_announcements, - timestamp.as_millis() as u64, + timestamp, mutator_set_accumulator, privacy, )) @@ -576,7 +575,7 @@ impl GlobalState { &mut self, receiver_data: &[UtxoReceiverData], fee: NeptuneCoins, - timestamp: u64, + timestamp: Timestamp, ) -> Result<( Vec, Vec<(Utxo, LockScript, MsMembershipProof)>, @@ -636,7 +635,7 @@ impl GlobalState { output_utxos: Vec, fee: NeptuneCoins, public_announcements: Vec, - timestamp: u64, + timestamp: Timestamp, mutator_set_accumulator: MutatorSetAccumulator, _privacy: bool, ) -> Transaction { @@ -646,7 +645,7 @@ impl GlobalState { outputs, public_announcements: public_announcements.clone(), fee, - timestamp: BFieldElement::new(timestamp), + timestamp, coinbase: None, mutator_set_hash: mutator_set_accumulator.hash(), }; @@ -994,9 +993,9 @@ impl GlobalState { let current_tip_header = self.chain.light_state().header(); let current_tip_digest = self.chain.light_state().kernel.mast_hash(); - let current_tip_info: (Digest, Duration, BlockHeight) = ( + let current_tip_info: (Digest, Timestamp, BlockHeight) = ( current_tip_digest, - Duration::from_millis(current_tip_header.timestamp.value()), + current_tip_header.timestamp, current_tip_header.height, ); @@ -1235,7 +1234,7 @@ mod global_state_tests { global_state_lock: &GlobalStateLock, receiver_data: &[UtxoReceiverData], fee: NeptuneCoins, - timestamp: u64, + timestamp: Timestamp, ) -> Result { // UTXO data: inputs, outputs, and supporting witness data let (inputs, spendable_utxos_and_mps, outputs, output_utxos) = global_state_lock @@ -1321,9 +1320,9 @@ mod global_state_tests { assert_ne!(monitored_utxos.len(), 0); // one month before release date, we should not be able to create the transaction - let launch = genesis_block.kernel.header.timestamp.value(); - let six_months: u64 = 6 * 30 * 24 * 60 * 60 * 1000; - let one_month: u64 = 30 * 24 * 60 * 60 * 1000; + let launch = genesis_block.kernel.header.timestamp; + let six_months = Timestamp::months(6); + let one_month = Timestamp::months(1); assert!(create_transaction_with_timestamp( &global_state_lock, &receiver_data, @@ -1345,11 +1344,11 @@ mod global_state_tests { assert!(tx.is_valid()); // but if we backdate the timestamp two months, not anymore! - tx.kernel.timestamp -= BFieldElement::new(2 * one_month); + tx.kernel.timestamp = tx.kernel.timestamp - Timestamp::months(2); // we can't test this yet; we don't have tasm code for time locks yet! // todo: uncomment the next line when we do. // assert!(!tx.is_valid()); - tx.kernel.timestamp += BFieldElement::new(2 * one_month); + tx.kernel.timestamp = tx.kernel.timestamp + Timestamp::months(2); assert_eq!( 2, @@ -1502,8 +1501,8 @@ mod global_state_tests { // 1. Create new block 1 and store it to the DB let genesis_block = Block::genesis_block(network); - let launch = genesis_block.kernel.header.timestamp.value(); - let seven_months = 7 * 30 * 24 * 60 * 60 * 1000; + let launch = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let (mock_block_1a, _, _) = make_mock_block(&genesis_block, None, other_receiver_address, rng.gen()); { @@ -1929,8 +1928,8 @@ mod global_state_tests { let bob_state_lock = get_mock_global_state(network, 3, wallet_secret_bob).await; let genesis_block = Block::genesis_block(network); - let launch = genesis_block.kernel.header.timestamp.value(); - let seven_months = 7 * 30 * 24 * 60 * 60 * 1000; + let launch = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let (mut block_1, cb_utxo, cb_output_randomness) = make_mock_block_with_valid_pow( &genesis_block, @@ -2002,8 +2001,8 @@ mod global_state_tests { tx_to_alice_and_bob, &genesis_block.kernel.body.mutator_set_accumulator, ); - let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); - assert!(block_1.is_valid(&genesis_block, now + Duration::from_millis(seven_months))); + let now = genesis_block.kernel.header.timestamp; + assert!(block_1.is_valid(&genesis_block, now + seven_months)); } println!("Accumulated transaction into block_1."); @@ -2146,7 +2145,7 @@ mod global_state_tests { public_announcement: PublicAnnouncement::default(), }, ]; - let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + let now = genesis_block.kernel.header.timestamp; let tx_from_alice = alice_state_lock .lock_guard_mut() .await @@ -2215,7 +2214,7 @@ mod global_state_tests { get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; let mut global_state = global_state_lock.lock_guard_mut().await; let genesis_block = Block::genesis_block(network); - let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + let now = genesis_block.kernel.header.timestamp; let wallet_secret = WalletSecret::new_random(); let receiving_address = wallet_secret.nth_generation_spending_key(0).to_address(); diff --git a/src/models/state/wallet/coin_with_possible_timelock.rs b/src/models/state/wallet/coin_with_possible_timelock.rs index 294ce3b6..e5d3ff44 100644 --- a/src/models/state/wallet/coin_with_possible_timelock.rs +++ b/src/models/state/wallet/coin_with_possible_timelock.rs @@ -1,37 +1,32 @@ -use std::{fmt::Display, time::Duration}; +use std::fmt::Display; -use chrono::DateTime; use itertools::Itertools; use num_traits::Zero; use serde::{Deserialize, Serialize}; -use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; +use crate::models::{ + blockchain::type_scripts::neptune_coins::NeptuneCoins, consensus::timestamp::Timestamp, +}; /// An amount of Neptune coins, with confirmation timestamp and (if time-locked) its /// release date. For reporting purposes. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CoinWithPossibleTimeLock { pub amount: NeptuneCoins, - pub confirmed: Duration, - pub release_date: Option, + pub confirmed: Timestamp, + pub release_date: Option, } impl Display for CoinWithPossibleTimeLock { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let confirmed_total_length = 25; - let confirmed = DateTime::from_timestamp_millis(self.confirmed.as_millis() as i64) - .unwrap() - .format("%Y-%m-%d %H:%M:%S") - .to_string(); + let confirmed = self.confirmed.format("%Y-%m-%d %H:%M:%S"); let confirmed_padding = " ".repeat(confirmed_total_length - confirmed.len()); let release_total_length = 25; let (release, release_padding) = match self.release_date { Some(date) => { - let string = DateTime::from_timestamp_millis(date.as_millis() as i64) - .unwrap() - .format("%Y-%m-%d %H:%M:%S") - .to_string(); + let string = date.format("%Y-%m-%d %H:%M:%S"); let string_padding = " ".repeat(release_total_length - string.len()); (string, string_padding) } @@ -112,12 +107,13 @@ impl CoinWithPossibleTimeLock { #[cfg(test)] mod test { - use std::time::Duration; use arbitrary::{Arbitrary, Unstructured}; use rand::{thread_rng, Rng, RngCore}; - use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; + use crate::models::{ + blockchain::type_scripts::neptune_coins::NeptuneCoins, consensus::timestamp::Timestamp, + }; use super::CoinWithPossibleTimeLock; @@ -134,11 +130,11 @@ mod test { NeptuneCoins::arbitrary(&mut Unstructured::new(&rng.gen::<[u8; 32]>())).unwrap() }, release_date: if rng.gen::() { - Some(Duration::from_millis(rng.next_u64() % (1 << 35))) + Some(rng.gen::()) } else { None }, - confirmed: Duration::from_millis(rng.next_u64() % (1 << 35)), + confirmed: rng.gen::(), }; coins.push(coin); } diff --git a/src/models/state/wallet/mod.rs b/src/models/state/wallet/mod.rs index 1c44cd8e..1649c0e3 100644 --- a/src/models/state/wallet/mod.rs +++ b/src/models/state/wallet/mod.rs @@ -344,8 +344,6 @@ impl WalletSecret { #[cfg(test)] mod wallet_tests { - use std::time::Duration; - use itertools::Itertools; use num_traits::CheckedSub; use rand::random; @@ -364,6 +362,7 @@ mod wallet_tests { use crate::models::blockchain::transaction::utxo::{LockScript, Utxo}; use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; + use crate::models::consensus::timestamp::Timestamp; use crate::models::state::wallet::utxo_notification_pool::UtxoNotifier; use crate::models::state::UtxoReceiverData; use crate::tests::shared::{ @@ -818,12 +817,12 @@ mod wallet_tests { get_mock_global_state(network, 2, premine_wallet).await; let mut premine_receiver_global_state = premine_receiver_global_state_lock.lock_guard_mut().await; - let launch = genesis_block.kernel.header.timestamp.value(); - let seven_months = Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000); + let launch = genesis_block.kernel.header.timestamp; + let seven_months = Timestamp::months(7); let preminers_original_balance = premine_receiver_global_state .get_wallet_status_for_tip() .await - .synced_unspent_available_amount(launch + seven_months.as_millis() as u64); + .synced_unspent_available_amount(launch + seven_months); assert!( !preminers_original_balance.is_zero(), "Premine must have non-zero synced balance" @@ -863,7 +862,7 @@ mod wallet_tests { }, }; let receiver_data_to_other = vec![receiver_data_12_to_other, receiver_data_one_to_other]; - let mut now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + let mut now = genesis_block.kernel.header.timestamp; let valid_tx = premine_receiver_global_state .create_transaction( receiver_data_to_other.clone(), @@ -915,7 +914,7 @@ mod wallet_tests { premine_receiver_global_state .get_wallet_status_for_tip() .await - .synced_unspent_available_amount(launch + seven_months.as_millis() as u64), + .synced_unspent_available_amount(launch + seven_months), "Preminer must have spent 15: 12 + 1 for sent, 2 for fees" ); @@ -1115,7 +1114,7 @@ mod wallet_tests { // one coinbase UTXO and one other UTXO let (mut block_3_b, cb_utxo, cb_sender_randomness) = make_mock_block(&block_2_b, None, own_address, rng.gen()); - now = Duration::from_millis(block_3_b.kernel.header.timestamp.value()); + now = block_3_b.kernel.header.timestamp; assert!( block_3_b.is_valid(&block_2_b, now), "Block must be valid before merging txs" diff --git a/src/models/state/wallet/monitored_utxo.rs b/src/models/state/wallet/monitored_utxo.rs index 92b3009e..0c95232a 100644 --- a/src/models/state/wallet/monitored_utxo.rs +++ b/src/models/state/wallet/monitored_utxo.rs @@ -1,6 +1,6 @@ -use crate::prelude::twenty_first; +use crate::{models::consensus::timestamp::Timestamp, prelude::twenty_first}; -use std::{collections::VecDeque, time::Duration}; +use std::collections::VecDeque; use crate::{ models::{blockchain::block::block_height::BlockHeight, state::archival_state::ArchivalState}, @@ -21,14 +21,14 @@ pub struct MonitoredUtxo { pub number_of_mps_per_utxo: usize, // hash of the block, if any, in which this UTXO was spent - pub spent_in_block: Option<(Digest, Duration, BlockHeight)>, + pub spent_in_block: Option<(Digest, Timestamp, BlockHeight)>, // hash of the block, if any, in which this UTXO was confirmed - pub confirmed_in_block: Option<(Digest, Duration, BlockHeight)>, + pub confirmed_in_block: Option<(Digest, Timestamp, BlockHeight)>, /// Indicator used to mark the UTXO as belonging to an abandoned fork /// Indicates what was the block tip when UTXO was marked as abandoned - pub abandoned_at: Option<(Digest, Duration, BlockHeight)>, + pub abandoned_at: Option<(Digest, Timestamp, BlockHeight)>, } impl MonitoredUtxo { diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 3a7184f1..ca80a315 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -1,6 +1,7 @@ use crate::models::blockchain::type_scripts::native_currency::NativeCurrency; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::consensus::tasm::program::ConsensusProgram; +use crate::models::consensus::timestamp::Timestamp; use crate::prelude::twenty_first; use anyhow::{bail, Result}; @@ -11,7 +12,6 @@ use std::collections::HashMap; use std::error::Error; use std::fmt::Debug; use std::path::PathBuf; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::fs::OpenOptions; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; use tracing::{debug, error, info, warn}; @@ -463,7 +463,7 @@ impl WalletState { let mut mutxo = MonitoredUtxo::new(utxo, self.number_of_mps_per_utxo); mutxo.confirmed_in_block = Some(( new_block.hash(), - Duration::from_millis(new_block.kernel.header.timestamp.value()), + new_block.kernel.header.timestamp, new_block.kernel.header.height, )); monitored_utxos.push(mutxo); @@ -528,7 +528,7 @@ impl WalletState { let mut spent_mutxo = monitored_utxos.get(*mutxo_list_index); spent_mutxo.spent_in_block = Some(( new_block.hash(), - Duration::from_millis(new_block.kernel.header.timestamp.value()), + new_block.kernel.header.timestamp, new_block.kernel.header.height, )); monitored_utxos.set(*mutxo_list_index, spent_mutxo); @@ -659,7 +659,7 @@ impl WalletState { &self, requested_amount: NeptuneCoins, tip_digest: Digest, - timestamp: u64, + timestamp: Timestamp, ) -> Result> { // TODO: Should return the correct spending keys associated with the UTXOs // We only attempt to generate a transaction using those UTXOs that have up-to-date @@ -709,10 +709,7 @@ impl WalletState { requested_amount: NeptuneCoins, tip_digest: Digest, ) -> Result> { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64; + let now = Timestamp::now(); self.allocate_sufficient_input_funds_from_lock(requested_amount, tip_digest, now) .await } @@ -1031,7 +1028,7 @@ mod tests { assert_eq!( ( block_12.hash(), - Duration::from_millis(block_12.kernel.header.timestamp.value()), + block_12.kernel.header.timestamp, 12u64.into() ), own_global_state diff --git a/src/models/state/wallet/wallet_status.rs b/src/models/state/wallet/wallet_status.rs index f7283f71..8c153115 100644 --- a/src/models/state/wallet/wallet_status.rs +++ b/src/models/state/wallet/wallet_status.rs @@ -1,11 +1,11 @@ use std::fmt::Display; -use std::time::{SystemTime, UNIX_EPOCH}; use itertools::Itertools; use serde::{Deserialize, Serialize}; use crate::models::blockchain::transaction::utxo::Utxo; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; +use crate::models::consensus::timestamp::Timestamp; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; #[derive(Clone, Debug, Deserialize, Serialize)] @@ -39,7 +39,7 @@ pub struct WalletStatus { } impl WalletStatus { - pub fn synced_unspent_available_amount(&self, timestamp: u64) -> NeptuneCoins { + pub fn synced_unspent_available_amount(&self, timestamp: Timestamp) -> NeptuneCoins { self.synced_unspent .iter() .map(|(wse, _msmp)| &wse.utxo) @@ -47,7 +47,7 @@ impl WalletStatus { .map(|utxo| utxo.get_native_currency_amount()) .sum::() } - pub fn synced_unspent_timelocked_amount(&self, timestamp: u64) -> NeptuneCoins { + pub fn synced_unspent_timelocked_amount(&self, timestamp: Timestamp) -> NeptuneCoins { self.synced_unspent .iter() .map(|(wse, _msmp)| &wse.utxo) @@ -77,10 +77,7 @@ impl WalletStatus { impl Display for WalletStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64; + let now = Timestamp::now(); let synced_unspent_available_count: usize = self .synced_unspent .iter() diff --git a/src/peer_loop.rs b/src/peer_loop.rs index 2bd76a07..17a47c31 100644 --- a/src/peer_loop.rs +++ b/src/peer_loop.rs @@ -1,4 +1,5 @@ use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::timestamp::Timestamp; use crate::prelude::twenty_first; use crate::connect_to_peers::close_peer_connected_callback; @@ -20,7 +21,7 @@ use itertools::Itertools; use std::cmp; use std::marker::Unpin; use std::net::SocketAddr; -use std::time::{SystemTime, UNIX_EPOCH}; +use std::time::SystemTime; use tokio::select; use tokio::sync::{broadcast, mpsc}; use tracing::{debug, error, info, warn}; @@ -113,7 +114,7 @@ impl PeerLoopHandler { "blocks" } ); - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let now = Timestamp::now(); let mut previous_block = &parent_of_first_block; for new_block in received_blocks.iter() { if !new_block.has_proof_of_work(previous_block) { @@ -148,8 +149,7 @@ impl PeerLoopHandler { info!( "Block with height {} is valid. mined: {}", new_block.kernel.header.height, - crate::utc_timestamp_to_localtime(new_block.kernel.header.timestamp.value()) - .to_string() + new_block.kernel.header.timestamp.standard_format() ); } @@ -384,7 +384,7 @@ impl PeerLoopHandler { "Got new block from peer {}, height {}, mined {}", self.peer_address, t_block.header.height, - crate::utc_timestamp_to_localtime(t_block.header.timestamp.value()).to_string() + t_block.header.timestamp.standard_format() ); let new_block_height = t_block.header.height; @@ -798,19 +798,11 @@ impl PeerLoopHandler { } // Get transaction timestamp - let tx_timestamp = match transaction.get_timestamp() { - Ok(ts) => ts, - Err(_) => { - warn!("Received tx with invalid timestamp"); - return Ok(KEEP_CONNECTION_ALIVE); - } - }; + let tx_timestamp = transaction.kernel.timestamp; // 2. Ignore if transaction is too old - let now = SystemTime::now(); - if tx_timestamp - < now - std::time::Duration::from_secs(MEMPOOL_TX_THRESHOLD_AGE_IN_SECS) - { + let now = Timestamp::now(); + if tx_timestamp < now - Timestamp::seconds(MEMPOOL_TX_THRESHOLD_AGE_IN_SECS) { // TODO: Consider punishing here warn!("Received too old tx"); return Ok(KEEP_CONNECTION_ALIVE); @@ -818,10 +810,7 @@ impl PeerLoopHandler { // 3. Ignore if transaction is too far into the future if tx_timestamp - > now - + std::time::Duration::from_secs( - MEMPOOL_IGNORE_TRANSACTIONS_THIS_MANY_SECS_AHEAD, - ) + > now + Timestamp::seconds(MEMPOOL_IGNORE_TRANSACTIONS_THIS_MANY_SECS_AHEAD) { // TODO: Consider punishing here warn!("Received tx too far into the future. Got timestamp: {tx_timestamp:?}"); diff --git a/src/rpc_server.rs b/src/rpc_server.rs index 345090d3..8ab46d4c 100644 --- a/src/rpc_server.rs +++ b/src/rpc_server.rs @@ -1,4 +1,5 @@ use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; +use crate::models::consensus::timestamp::Timestamp; use crate::models::state::wallet::coin_with_possible_timelock::CoinWithPossibleTimeLock; use crate::prelude::twenty_first; @@ -9,9 +10,6 @@ use std::collections::HashMap; use std::net::IpAddr; use std::net::SocketAddr; use std::str::FromStr; -use std::time::Duration; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; use tarpc::context; use tokio::sync::mpsc::error::SendError; use tracing::{error, info}; @@ -97,7 +95,7 @@ pub trait RPC { async fn synced_balance() -> NeptuneCoins; /// Get the client's wallet transaction history - async fn history() -> Vec<(Digest, BlockHeight, Duration, NeptuneCoins)>; + async fn history() -> Vec<(Digest, BlockHeight, Timestamp, NeptuneCoins)>; /// Return information about funds in the wallet async fn wallet_status() -> WalletStatus; @@ -310,10 +308,7 @@ impl RPC for NeptuneRPCServer { } async fn amount_leq_synced_balance(self, _ctx: context::Context, amount: NeptuneCoins) -> bool { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64; + let now = Timestamp::now(); // test inequality let wallet_status = self .state @@ -325,10 +320,7 @@ impl RPC for NeptuneRPCServer { } async fn synced_balance(self, _context: tarpc::context::Context) -> NeptuneCoins { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64; + let now = Timestamp::now(); let wallet_status = self .state .lock_guard() @@ -395,11 +387,11 @@ impl RPC for NeptuneRPCServer { async fn history( self, _context: tarpc::context::Context, - ) -> Vec<(Digest, BlockHeight, Duration, NeptuneCoins)> { + ) -> Vec<(Digest, BlockHeight, Timestamp, NeptuneCoins)> { let history = self.state.lock_guard().await.get_balance_history().await; // sort - let mut display_history: Vec<(Digest, BlockHeight, Duration, NeptuneCoins)> = history + let mut display_history: Vec<(Digest, BlockHeight, Timestamp, NeptuneCoins)> = history .iter() .map(|(h, t, bh, a)| (*h, *bh, *t, *a)) .collect::>(); @@ -413,10 +405,7 @@ impl RPC for NeptuneRPCServer { self, _context: tarpc::context::Context, ) -> DashBoardOverviewDataFromClient { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64; + let now = Timestamp::now(); let state = self.state.lock_guard().await; let tip_header = state.chain.light_state().header().clone(); let wallet_status = state.get_wallet_status_for_tip().await; @@ -503,7 +492,7 @@ impl RPC for NeptuneRPCServer { let coins = amount.to_native_coins(); let utxo = Utxo::new(address.lock_script(), coins); - let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + let now = Timestamp::now(); let state = self.state.lock_guard().await; let block_height = state.chain.light_state().header().height; diff --git a/src/tests/shared.rs b/src/tests/shared.rs index 6217c75e..a03b6170 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -2,6 +2,7 @@ use crate::models::blockchain::transaction; use crate::models::blockchain::transaction::primitive_witness::SaltedUtxos; use crate::models::blockchain::type_scripts::neptune_coins::pseudorandom_amount; use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; +use crate::models::consensus::timestamp::Timestamp; use crate::models::consensus::ValidityTree; use crate::prelude::twenty_first; @@ -24,15 +25,8 @@ use rand::RngCore; use rand::SeedableRng; use std::path::Path; use std::path::PathBuf; -use std::{ - collections::HashMap, - env, - net::SocketAddr, - pin::Pin, - str::FromStr, - sync::Arc, - time::{SystemTime, UNIX_EPOCH}, -}; +use std::time::SystemTime; +use std::{collections::HashMap, env, net::SocketAddr, pin::Pin, str::FromStr, sync::Arc}; use tasm_lib::triton_vm::proof::Proof; use tasm_lib::twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator; use tokio::sync::{broadcast, mpsc}; @@ -767,19 +761,14 @@ pub fn make_mock_transaction_with_generation_key( .iter() .map(|x| x.public_announcement.clone()) .collect_vec(); - let timestamp: u64 = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() - .try_into() - .unwrap(); + let timestamp = Timestamp::now(); let kernel = TransactionKernel { inputs, outputs, public_announcements, fee, - timestamp: BFieldElement::new(timestamp), + timestamp, coinbase: None, mutator_set_hash: tip_msa.hash(), }; @@ -828,14 +817,7 @@ pub fn make_mock_transaction( inputs: Vec, outputs: Vec, ) -> Transaction { - let timestamp: BFieldElement = BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Got bad timestamp") - .as_millis() - .try_into() - .unwrap(), - ); + let timestamp = Timestamp::now(); Transaction { kernel: TransactionKernel { @@ -860,18 +842,11 @@ pub fn make_mock_transaction_with_wallet( outputs: Vec, fee: NeptuneCoins, _wallet_state: &WalletState, - timestamp: Option, + timestamp: Option, ) -> Transaction { let timestamp = match timestamp { Some(ts) => ts, - None => BFieldElement::new( - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Timestamp generation must work") - .as_millis() - .try_into() - .expect("Timestamp in ms must be representable as u64"), - ), + None => Timestamp::now(), }; let kernel = TransactionKernel { inputs, @@ -899,7 +874,7 @@ pub fn make_mock_transaction_with_wallet( pub fn make_mock_block( previous_block: &Block, // target_difficulty: Option>, - block_timestamp: Option, + block_timestamp: Option, coinbase_beneficiary: generation_address::ReceivingAddress, seed: [u8; 32], ) -> (Block, Utxo, Digest) { @@ -925,7 +900,7 @@ pub fn make_mock_block( let block_timestamp = match block_timestamp { Some(ts) => ts, - None => previous_block.kernel.header.timestamp.value() + TARGET_BLOCK_INTERVAL, + None => previous_block.kernel.header.timestamp + Timestamp::millis(TARGET_BLOCK_INTERVAL), }; let tx_kernel = TransactionKernel { @@ -933,7 +908,7 @@ pub fn make_mock_block( outputs: vec![coinbase_addition_record], public_announcements: vec![], fee: NeptuneCoins::zero(), - timestamp: BFieldElement::new(block_timestamp), + timestamp: block_timestamp, coinbase: Some(coinbase_amount), mutator_set_hash: previous_mutator_set.hash(), }; @@ -990,7 +965,7 @@ pub fn make_mock_block( pub fn make_mock_block_with_valid_pow( previous_block: &Block, - block_timestamp: Option, + block_timestamp: Option, coinbase_beneficiary: generation_address::ReceivingAddress, seed: [u8; 32], ) -> (Block, Utxo, Digest) { @@ -1017,7 +992,7 @@ pub fn make_mock_block_with_valid_pow( pub fn make_mock_block_with_invalid_pow( previous_block: &Block, - block_timestamp: Option, + block_timestamp: Option, coinbase_beneficiary: generation_address::ReceivingAddress, seed: [u8; 32], ) -> (Block, Utxo, Digest) { From 990cf7770d3b1fb4eddbe45bccb9c38c75b17aa9 Mon Sep 17 00:00:00 2001 From: Alan Szepieniec Date: Thu, 14 Mar 2024 17:08:25 +0100 Subject: [PATCH 4/4] test: Implement `arbitrary_between` for `Timestamp` Deconvolutes tests. --- .../blockchain/type_scripts/time_lock.rs | 19 +++++++++---------- src/models/consensus/timestamp.rs | 7 +++++++ 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index a51eb4cd..be25fa8f 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -576,8 +576,8 @@ impl Arbitrary for TimeLockWitness { #[cfg(test)] mod test { + use num_traits::Zero; use proptest::{collection::vec, strategy::Just}; - use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; use test_strategy::proptest; use crate::models::{ @@ -586,15 +586,14 @@ mod test { }; use super::TimeLockWitness; - use itertools::Itertools; #[proptest] fn test_unlocked( #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, #[strategy(1usize..=3)] _num_public_announcements: usize, - #[strategy(vec(Just(0u64), #_num_inputs))] _release_dates: Vec, - #[strategy(TimeLockWitness::arbitrary_with((#_release_dates.iter().cloned().map(BFieldElement::new).map(Timestamp).collect_vec(), #_num_outputs, #_num_public_announcements)))] + #[strategy(vec(Just(Timestamp::zero()), #_num_inputs))] _release_dates: Vec, + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] time_lock_witness: TimeLockWitness, ) { assert!( @@ -613,9 +612,9 @@ mod test { #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, #[strategy(1usize..=3)] _num_public_announcements: usize, - #[strategy(vec(Timestamp::now().0.value()+Timestamp::days(1).0.value()..Timestamp::now().0.value()+Timestamp::days(7).0.value(), #_num_inputs))] - _release_dates: Vec, - #[strategy(TimeLockWitness::arbitrary_with((#_release_dates.iter().cloned().map(BFieldElement::new).map(Timestamp).collect_vec(), #_num_outputs, #_num_public_announcements)))] + #[strategy(vec(Timestamp::arbitrary_between(Timestamp::now()+Timestamp::days(1),Timestamp::now()+Timestamp::days(7)), #_num_inputs))] + _release_dates: Vec, + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] time_lock_witness: TimeLockWitness, ) { println!("now: {}", Timestamp::now()); @@ -635,9 +634,9 @@ mod test { #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, #[strategy(1usize..=3)] _num_public_announcements: usize, - #[strategy(vec(Timestamp::now().0.value()-Timestamp::days(7).0.value()..Timestamp::now().0.value()-Timestamp::days(1).0.value(), #_num_inputs))] - _release_dates: Vec, - #[strategy(TimeLockWitness::arbitrary_with((#_release_dates.iter().cloned().map(BFieldElement::new).map(Timestamp).collect_vec(), #_num_outputs, #_num_public_announcements)))] + #[strategy(vec(Timestamp::arbitrary_between(Timestamp::now()-Timestamp::days(7),Timestamp::now()-Timestamp::days(1)), #_num_inputs))] + _release_dates: Vec, + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] time_lock_witness: TimeLockWitness, ) { println!("now: {}", Timestamp::now()); diff --git a/src/models/consensus/timestamp.rs b/src/models/consensus/timestamp.rs index 25954169..784e811a 100644 --- a/src/models/consensus/timestamp.rs +++ b/src/models/consensus/timestamp.rs @@ -8,6 +8,7 @@ use arbitrary::Arbitrary; use chrono::{DateTime, Local, NaiveDateTime, Utc}; use get_size::GetSize; use num_traits::Zero; +use proptest::strategy::{BoxedStrategy, Strategy}; use rand::distributions::{Distribution, Standard}; use serde::{Deserialize, Serialize}; use tasm_lib::twenty_first::shared_math::{ @@ -108,6 +109,12 @@ impl Timestamp { let offset: DateTime = DateTime::from(utc); offset.to_rfc2822() } + + pub fn arbitrary_between(start: Timestamp, stop: Timestamp) -> BoxedStrategy { + (start.0.value()..stop.0.value()) + .prop_map(|v| Timestamp(BFieldElement::new(v))) + .boxed() + } } impl Display for Timestamp {