diff --git a/benchmarks/tasm_neptune_transaction_removal_records_integrity.json b/benchmarks/tasm_neptune_transaction_removal_records_integrity.json index 52f365ac3..721b65c5f 100644 --- a/benchmarks/tasm_neptune_transaction_removal_records_integrity.json +++ b/benchmarks/tasm_neptune_transaction_removal_records_integrity.json @@ -3,7 +3,7 @@ "name": "tasm_neptune_transaction_removal_records_integrity", "clock_cycle_count": 30848, "hash_table_height": 5621, - "u32_table_height": 13828, + "u32_table_height": 13792, "case": "CommonCase" } ] \ No newline at end of file diff --git a/src/bin/dashboard_src/overview_screen.rs b/src/bin/dashboard_src/overview_screen.rs index 0697b3d05..12cd36365 100644 --- a/src/bin/dashboard_src/overview_screen.rs +++ b/src/bin/dashboard_src/overview_screen.rs @@ -33,7 +33,8 @@ use super::screen::Screen; #[derive(Debug, Clone)] pub struct OverviewData { - synced_balance: Option, + available_balance: Option, + timelocked_balance: Option, confirmations: Option, synchronization_percentage: Option, @@ -66,7 +67,8 @@ pub struct OverviewData { impl OverviewData { pub fn new(network: Network, listen_address: Option) -> Self { Self { - synced_balance: Default::default(), + available_balance: Default::default(), + timelocked_balance: Default::default(), confirmations: Default::default(), synchronization_percentage: Default::default(), network, @@ -93,7 +95,8 @@ impl OverviewData { } pub fn test() -> Self { OverviewData { - synced_balance: Some(NeptuneCoins::zero()), + available_balance: Some(NeptuneCoins::zero()), + timelocked_balance: Some(NeptuneCoins::zero()), confirmations: Some(17.into()), synchronization_percentage: Some(99.5), @@ -202,7 +205,8 @@ impl OverviewScreen { own_overview_data.peer_count=resp.peer_count; own_overview_data.authenticated_peer_count=Some(0); own_overview_data.syncing=resp.syncing; - own_overview_data.synced_balance = Some(resp.synced_balance); + own_overview_data.available_balance = Some(resp.available_balance); + own_overview_data.timelocked_balance = Some(resp.timelocked_balance); own_overview_data.is_mining = resp.is_mining; own_overview_data.confirmations = resp.confirmations; } @@ -358,13 +362,17 @@ impl Widget for OverviewScreen { // balance lines.push(format!( - "synced balance: {} {}", - dashifnotset!(data.synced_balance), + "available balance: {} {}", + dashifnotset!(data.available_balance), match data.confirmations { Some(c) => format!("({} confirmations)", c), None => " ".to_string(), }, )); + lines.push(format!( + "time-locked balance: {}", + dashifnotset!(data.timelocked_balance), + )); lines.push(format!( "synchronization: {}", match data.synchronization_percentage { diff --git a/src/bin/neptune-cli.rs b/src/bin/neptune-cli.rs index a3daf91f6..6e1b6d54d 100644 --- a/src/bin/neptune-cli.rs +++ b/src/bin/neptune-cli.rs @@ -1,4 +1,5 @@ use neptune_core::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; +use neptune_core::models::state::wallet::coin_with_possible_timelock::CoinWithPossibleTimeLock; use neptune_core::prelude::twenty_first; use anyhow::{bail, Result}; @@ -44,6 +45,7 @@ enum Command { SyncedBalance, WalletStatus, OwnReceivingAddress, + ListCoins, MempoolTxCount, MempoolSize, @@ -262,6 +264,10 @@ async fn main() -> Result<()> { | Command::ImportSeedPhrase { .. } => unreachable!("Case should be handled earlier."), /******** READ STATE ********/ + Command::ListCoins => { + let list = client.list_own_coins(ctx).await?; + println!("{}", CoinWithPossibleTimeLock::report(&list)); + } Command::Network => { let network = client.network(ctx).await?; println!("{network}") diff --git a/src/mine_loop.rs b/src/mine_loop.rs index 614b8d90a..1b458ec4b 100644 --- a/src/mine_loop.rs +++ b/src/mine_loop.rs @@ -4,6 +4,7 @@ use crate::models::blockchain::block::block_height::BlockHeight; use crate::models::blockchain::block::mutator_set_update::*; use crate::models::blockchain::block::*; use crate::models::blockchain::shared::*; +use crate::models::blockchain::transaction; use crate::models::blockchain::transaction::transaction_kernel::TransactionKernel; use crate::models::blockchain::transaction::utxo::*; use crate::models::blockchain::transaction::validity::TransactionValidationLogic; @@ -42,7 +43,6 @@ use twenty_first::shared_math::digest::Digest; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use twenty_first::util_types::emojihash_trait::Emojihash; -use self::primitive_witness::PrimitiveWitness; use self::primitive_witness::SaltedUtxos; const MOCK_MAX_BLOCK_SIZE: u32 = 1_000_000; @@ -51,6 +51,7 @@ const MOCK_MAX_BLOCK_SIZE: u32 = 1_000_000; fn make_block_template( previous_block: &Block, transaction: Transaction, + timestamp: Duration, ) -> (BlockHeader, BlockBody) { let additions = transaction.kernel.outputs.clone(); let removals = transaction.kernel.inputs.clone(); @@ -62,7 +63,7 @@ fn make_block_template( // the function such that the next mutator set accumulator is calculated. let mutator_set_update = MutatorSetUpdate::new(removals, additions); mutator_set_update - .apply(&mut next_mutator_set_accumulator) + .apply_to_accumulator(&mut next_mutator_set_accumulator) .expect("Mutator set mutation must work"); let mut block_mmra = previous_block.kernel.body.block_mmr_accumulator.clone(); @@ -79,10 +80,7 @@ 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 = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Got bad time timestamp in mining process") - .as_millis() as u64; + let mut block_timestamp = timestamp.as_millis() as u64; if block_timestamp < previous_block.kernel.header.timestamp.value() { warn!("Received block is timestamped in the future; mining on future-timestamped block."); block_timestamp = previous_block.kernel.header.timestamp.value() + 1; @@ -181,6 +179,7 @@ fn make_coinbase_transaction( wallet_secret: &WalletSecret, block_height: BlockHeight, mutator_set_accumulator: MutatorSetAccumulator, + timestamp: Duration, ) -> (Transaction, Digest) { let sender_randomness: Digest = wallet_secret.generate_sender_randomness(block_height, receiver_digest); @@ -200,24 +199,17 @@ fn make_coinbase_transaction( receiver_digest, ); - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Got bad time timestamp in mining process") - .as_millis() - .try_into() - .expect("Must call this function before 584 million years from genesis."); - let kernel = TransactionKernel { inputs: vec![], outputs: vec![coinbase_addition_record], public_announcements: vec![], fee: NeptuneCoins::zero(), - timestamp: BFieldElement::new(timestamp), + timestamp: BFieldElement::new(timestamp.as_millis() as u64), coinbase: Some(coinbase_amount), mutator_set_hash: mutator_set_accumulator.hash(), }; - let primitive_witness = PrimitiveWitness { + let primitive_witness = transaction::primitive_witness::PrimitiveWitness { input_utxos: SaltedUtxos::empty(), type_scripts: vec![TypeScript::native_currency()], input_lock_scripts: vec![], @@ -225,14 +217,13 @@ fn make_coinbase_transaction( input_membership_proofs: vec![], output_utxos: SaltedUtxos::new(vec![coinbase_utxo.clone()]), mutator_set_accumulator, - kernel, + kernel: kernel.clone(), }; - let transaction_validation_logic = - TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); + let transaction_validation_logic = TransactionValidationLogic::from(primitive_witness); ( Transaction { - kernel: primitive_witness.kernel, - witness: TransactionWitness::ValidationLogic(transaction_validation_logic), + kernel, + witness: transaction_validation_logic, }, sender_randomness, ) @@ -244,6 +235,7 @@ fn make_coinbase_transaction( fn create_block_transaction( latest_block: &Block, global_state: &GlobalState, + timestamp: Duration, ) -> (Transaction, ExpectedUtxo) { let block_capacity_for_transactions = SIZE_20MB_IN_BYTES; @@ -274,6 +266,7 @@ fn create_block_transaction( &global_state.wallet_state.wallet_secret, next_block_height, latest_block.kernel.body.mutator_set_accumulator.clone(), + timestamp, ); debug!( @@ -331,11 +324,14 @@ 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 (transaction, coinbase_utxo_info) = create_block_transaction( &latest_block, global_state_lock.lock_guard().await.deref(), + now, ); - let (block_header, block_body) = make_block_template(&latest_block, transaction); + let (block_header, block_body) = + make_block_template(&latest_block, transaction, now); let miner_task = mine_block( block_header, block_body, @@ -418,7 +414,8 @@ pub async fn mine( // The block, however, *must* be valid on other parameters. So here, we should panic // if it is not. - assert!(new_block_info.block.is_valid(&latest_block), "Own mined block must be valid. Failed validity check after successful PoW check."); + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); + 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()); @@ -463,7 +460,7 @@ mod mine_loop_tests { async fn block_template_is_valid_test() -> Result<()> { // Verify that a block template made with transaction from the mempool is a valid block let premine_receiver_global_state_lock = - get_mock_global_state(Network::Alpha, 2, None).await; + get_mock_global_state(Network::Alpha, 2, WalletSecret::devnet_wallet()).await; let mut premine_receiver_global_state = premine_receiver_global_state_lock.lock_guard_mut().await; assert!( @@ -473,8 +470,9 @@ mod mine_loop_tests { // Verify constructed coinbase transaction and block template when mempool is empty let genesis_block = Block::genesis_block(); + 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); + create_block_transaction(&genesis_block, &premine_receiver_global_state, now); assert_eq!( 1, transaction_empty_mempool.kernel.outputs.len(), @@ -485,14 +483,14 @@ mod mine_loop_tests { "Coinbase transaction with empty mempool must have zero inputs" ); let (block_header_template_empty_mempool, block_body_empty_mempool) = - make_block_template(&genesis_block, transaction_empty_mempool); + make_block_template(&genesis_block, transaction_empty_mempool, now); let block_template_empty_mempool = Block::new( block_header_template_empty_mempool, block_body_empty_mempool, None, ); assert!( - block_template_empty_mempool.is_valid(&genesis_block), + block_template_empty_mempool.is_valid(&genesis_block, now), "Block template created by miner with empty mempool must be valid" ); @@ -516,6 +514,7 @@ mod mine_loop_tests { }), ], NeptuneCoins::new(1), + now + Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000), ) .await .unwrap(); @@ -526,7 +525,11 @@ mod mine_loop_tests { // Build transaction let (transaction_non_empty_mempool, _new_coinbase_sender_randomness) = - create_block_transaction(&genesis_block, &premine_receiver_global_state); + create_block_transaction( + &genesis_block, + &premine_receiver_global_state, + now + Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000 + 1000), + ); assert_eq!( 3, transaction_non_empty_mempool.kernel.outputs.len(), @@ -535,11 +538,17 @@ mod mine_loop_tests { assert_eq!(1, transaction_non_empty_mempool.kernel.inputs.len(), "Transaction for block with non-empty mempool must contain one input: the genesis UTXO being spent"); // Build and verify block template - let (block_header_template, block_body) = - make_block_template(&genesis_block, transaction_non_empty_mempool); + 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), + ); let block_template_non_empty_mempool = Block::new(block_header_template, block_body, None); assert!( - block_template_non_empty_mempool.is_valid(&genesis_block), + block_template_non_empty_mempool.is_valid( + &genesis_block, + now + Duration::from_millis(7 * 30 * 24 * 60 * 60 * 1000 + 2000) + ), "Block template created by miner with non-empty mempool must be valid" ); diff --git a/src/models/blockchain/block/block_body.rs b/src/models/blockchain/block/block_body.rs index a69cd2dc2..a539f647c 100644 --- a/src/models/blockchain/block/block_body.rs +++ b/src/models/blockchain/block/block_body.rs @@ -58,7 +58,7 @@ impl MastHash for BlockBody { fn mast_sequences(&self) -> Vec> { vec![ - self.transaction.encode(), + self.transaction.kernel.encode(), self.mutator_set_accumulator.encode(), self.lock_free_mmr_accumulator.encode(), self.block_mmr_accumulator.encode(), diff --git a/src/models/blockchain/block/block_kernel.rs b/src/models/blockchain/block/block_kernel.rs index 24dd8f3ea..01be44337 100644 --- a/src/models/blockchain/block/block_kernel.rs +++ b/src/models/blockchain/block/block_kernel.rs @@ -1,6 +1,8 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; -use tasm_lib::twenty_first::shared_math::bfield_codec::BFieldCodec; +use tasm_lib::twenty_first::shared_math::{ + b_field_element::BFieldElement, bfield_codec::BFieldCodec, +}; use crate::models::consensus::mast_hash::{HasDiscriminant, MastHash}; @@ -28,10 +30,11 @@ impl HasDiscriminant for BlockKernelField { impl MastHash for BlockKernel { type FieldEnum = BlockKernelField; - fn mast_sequences(&self) -> Vec> { - vec![ + fn mast_sequences(&self) -> Vec> { + let sequences = vec![ self.header.mast_hash().encode(), self.body.mast_hash().encode(), - ] + ]; + sequences } } diff --git a/src/models/blockchain/block/mod.rs b/src/models/blockchain/block/mod.rs index 09bb48593..a284104fe 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::{ValidityAstType, ValidityTree, WitnessType}; use crate::prelude::twenty_first; use get_size::GetSize; @@ -9,7 +10,7 @@ use num_traits::{abs, Zero}; use serde::{Deserialize, Serialize}; use std::cmp::max; -use std::time::{Duration, SystemTime}; +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; @@ -41,10 +42,11 @@ use self::mutator_set_update::MutatorSetUpdate; use self::transfer_block::TransferBlock; use super::transaction::transaction_kernel::TransactionKernel; use super::transaction::utxo::Utxo; +use super::transaction::validity::TransactionValidationLogic; use super::transaction::Transaction; use super::type_scripts::neptune_coins::NeptuneCoins; +use super::type_scripts::time_lock::TimeLock; use crate::models::blockchain::shared::Hash; -use crate::models::consensus::Witness; use crate::models::state::wallet::address::generation_address::{self, ReceivingAddress}; use crate::models::state::wallet::WalletSecret; use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; @@ -122,6 +124,11 @@ impl Block { reward } + // 1 July 2024 (might be revised before then) + pub fn launch() -> u64 { + 1719792000000u64 + } + pub fn genesis_block() -> Self { let mut genesis_mutator_set = MutatorSetAccumulator::default(); let mut ms_update = MutatorSetUpdate::default(); @@ -132,27 +139,29 @@ impl Block { .map(|(_receiving_address, amount)| *amount) .sum(); - // This is the UNIX timestamp in ms when this code was written the 1st time - let timestamp: BFieldElement = BFieldElement::new(1655916990000u64); - let mut genesis_coinbase_tx = Transaction { kernel: TransactionKernel { inputs: vec![], outputs: vec![], fee: NeptuneCoins::new(0), - timestamp, + timestamp: BFieldElement::new(Self::launch()), public_announcements: vec![], coinbase: Some(total_premine_amount), mutator_set_hash: MutatorSetAccumulator::new().hash(), }, - witness: Witness::Faith, + witness: TransactionValidationLogic { + vast: ValidityTree { + vast_type: ValidityAstType::Axiom, + witness_type: WitnessType::Faith, + }, + maybe_primitive_witness: None, + }, }; - for (receiving_address, amount) in premine_distribution { - // generate utxo - let utxo = Utxo::new_native_coin(receiving_address.lock_script(), amount); + for ((receiving_address, _amount), utxo) in + premine_distribution.iter().zip(Self::premine_utxos()) + { let utxo_digest = Hash::hash(&utxo); - // generate randomness for mutator set commitment // Sender randomness cannot be random because there is no sender. let bad_randomness = Digest::default(); @@ -179,7 +188,7 @@ impl Block { version: BFieldElement::zero(), height: BFieldElement::zero().into(), prev_block_digest: Digest::default(), - timestamp, + timestamp: BFieldElement::new(Self::launch()), nonce: [ BFieldElement::zero(), BFieldElement::zero(), @@ -194,7 +203,7 @@ impl Block { Self::new(header, body, None) } - pub fn premine_distribution() -> Vec<(generation_address::ReceivingAddress, NeptuneCoins)> { + fn premine_distribution() -> Vec<(generation_address::ReceivingAddress, NeptuneCoins)> { // The premine UTXOs can be hardcoded here. let authority_wallet = WalletSecret::devnet_wallet(); let authority_receiving_address = @@ -205,9 +214,23 @@ impl Block { // also for testing, but for internal use only (ReceivingAddress::from_bech32m("nolgam1t6h52ck34mkvvmkk8nnzesf5sdcks3mlj23k8hgp5gc39qaxx76qnltllx465np340n0mf9zrv2e04425q69xlhjgy35v3zu7jmnljev9n38t2a86d9sqq84g8y9egy23etpkewp4ad64s66qq9cruyp0r0vz50urcalgxerv6xcuet6j5tcdx6tqm6d772dxu29r6kq8mkzkyrc07072rlvkx4tkmwy29aqq8qmwwd0n4at3qllgvd427um3jsjed696rddert6dzlamqtn66mz997xt8nslrq8dqvl2nx4k7vu50ul7584m7243pdzdczgnxcd0a8q8aspfd66s5spaa5nk8sqfh29htak8lzf853edgqw99fu4v4ess3d9z0gcqjpclks9p2w5srta9n65r5w2rj89jmagtuklz838lj726frzdvlfj7t992hz8n355raxy2xnm4fpfr20zvk38caatsd74lzx370mfhqrakf6achx5fv858wpchjlmu3h55s5kqkmfu0zhw05wfx7meu33fnmw0fju6p0m940nfrsqkv0e8q25g3sgjk4t0qfun0st7h2k4ef6cau3zyrc5dsqukvzwd85kxxf9ksk6jw7k5ny7wku6wf90mx5xyd7p6q5w6eu4wxxfeqryyfw2rdprr7fkzg9hrt97s4hn9cgpr6qz8x0j59gm885ekde9czanpksqq0c0kmefzfha3lqw8v2xeme5nmf93u59z8luq4wprlxj6v7mpp80t3sjvmv3a6t2kxsh9qaw9spj789ft8jswzm2kmfywxn80caccqf4d38kkjg5ahdrkmfvec242rg47ewzwsfy590hxyvz5v3dpg2a99vwc20a749rmygj74k2uw794t66dz0n9chmhd47gg84y8qc62jvjl8num4j7s2c0gtc88t3pun4zwuq55vf66mg4n8urn50lm7ww4he5x5ya4yyaqlrn2ag5sdnqt46magvw90hh9chyq3q9qc36pq4tattn6lvzfjp9trxuske84yttf6pa3le9z0z8y06gv7925dshhfjn4y5y3aykfg2g7ujrlly8dgpk3srlvq0zmdvgu5jsxwqvngvp6fh6he8fyrlqgrs58qklrg3zyu2jl9nrp2hdvj3hwh29fk5mjl9tpjx0tnyys5gkqlvxxhel4yh53ms0rxpkw3sa6teqgpe4yej5sk7edyqn7w8xr4mgm2asww53gzv95fwpud7mzg4rrnpvdk40m0vna8w8y0w9y240r6m7ja58gfk3stfra9qsm0lt7npkv4w0ghzypdrrg04kp7kkepnm4qmwmjxdg2tx3ejtdmzp0w08alv7x3zxgxsu35yhlvrnkpl9mxgejkfcxdgccper4f7llaaux9hcpul5uy47lhr065qwkgxc6jfylq5raqeczryz089syr4aj7z908e4e3t49qd40x3ueyrgxcdj37dkd5ysezj45kgtv546e7m3fj8ga920lztrgmmx0a98qwnk2ep5k9qh2x05mm5snu5d88lm4lrad8hc639jx97hrx9mywkw6c7yvj9jv0mjmsq0xqpqt0kc4hsh24kndhtsc0ezfzw9h79mjw239s804t2f4jucd3x57mvvnsyp82xy9jvp4yzlq5qhrpu87frkfwkx62r8rjsdkdlx4yhss2ly4q8425ta3je6rym35lapxesd9dhsj44pfhmq92g4tmfr8qnajpn2cgj8ngtzrkc9ygsvx76633p8ksru7g8cda5dfnhf50ax47rde5fhnk8dt7k5sltkhknha697gyqsjg4hytslxmaazdjqj4earaf098uz6gpcgu27zsy4v5arc3vjmum90ngf8e00exjr4nsqs3wr4w93h42ucnllyu5ck09yundjkjqsqetrhzvc3q0smssg6vcw9hlns363grqyt92azpvml632wffpuq5wtsh9vxwdse0g0w0wl3e320hnp3vlmzde3c8xa42yye90gnmmyjdq5atmlnulga4pcapk4t6ut82w057ed3rawx42vn7rl5kzyg84cvulg8yfjeu3ff0wprytkhk85dr63u9elq5ju0c9vd2yyjkqnhxh6xwxnt4nw32pefm9aengdasjn7lsyaeldz93spfnn02uke83xkwytj0wkxhgknde5jnjgg6yegwuw8rklvh6cvyvzqkgwaj857cz7xt3u8mhxlh8xevud3vj5dvq6kpxqd4jftt5h4gcmf9qpj3e2nw87j9une3vu75ahewdrqg7avfquw79fva59f8f3xpmk6lpmlkx9x7ejaw97f8nu86r2yhaepr50cdew82c3fmpnma2gr5vatjy3luqsyf8fpqp2zrjzcymemt3f3t99rn689ucyaj8vc2eapgw4knjyaque29hk3t7swcdvrwcf5myg33ghmg2s8xrqjwzeghzmqq68278lrw5rxn4jf3y93z7ztuwz67s0qa5lldcqe44qsshpuxx36dmna5cn7yy5v5f449gf26hygmj6qk8hm7rkvv44w3cu9fdv7sq0hqy67p3tvyxc8fl640z7pdsjfraznvqpnvcepggdnf3qypgs8vu82wsj2yd8nkhfv6sv6xs3wf5d7nkqsd5k8ehk7dtfqnsvcz26yazc32cv669qn7dhxr25j0etmmz7xh8azj7dn0d4u309m0rc2yhfegds60smuqtxn4l4nhmdqj9x6se4sultl5cwy4qja66cvnjz6mqwqet4n5zcswywqd6gcpec4q2vek9g4086ys4x35hwa47dk3zj2m03yuqz7ap66dah3r73j96q00cwmqw0lxvvqq4u0kvt6vrc0urd2hfhrxkrkmr9yx48uw94vmnjyq7sgyc0szkyuq07cjhg0fhx5z5mr9ua24wx9qnh32cjult3mu8kzhlj7se2nm4jr937j64656q7vp98dh9dhvlge8p02ejse5r0nsk22aa5cexvuqcaulnxw690vm3vdagdckfwps06jjd49kd4ls4jkf0nxkhqx2rm73pcepr4u6xjxw2fhjptk95tt0rq2ramq57lfg3sw3tsee2af355lt53w4f5wmpcvctsntyl2sp8m04l3nds7acv4uqnznudmkasgdf7l9df4484ym2njjzy0c26v2zv7pkv30f06uuptdvuxmgnuqcgd4els7gehp0fwxam0vskt34e3z3kfft6kkdz2c7ftn3dcvz5wvpwqf8458ade6995vdkxkalqzfs5epjfnn3c27mnzlx6cv5fhlephxpa3mj3hu6wafd8em8jhzcguru797p6m2fes55ha23putxrtly4wufl6rpp3ydta57zcxl40pvhpps7sgr7zc2cvz57xdlxpvclsjdgp5q3up9tu5csfdkaa762mk7zrqad93506l0kj".to_string(), Network::Alpha).unwrap(), NeptuneCoins::new(1337)), + (ReceivingAddress::from_bech32m("nolgam1hfgnle0202fgz75wh5cqpxkzz29775pqudt9z9v0s6h2e3gkfqkgv3xqn4xfq809k880cspd4dw4mmmcy3dus2pyxwcfysle3hsw2qc62qk3d4hesv56q45d539s28e267mzdvcgyrnwuz358edzjcpzwkep3wxccxrss7qqj0806uff26waqg2z37g7g8erew0eyaq83lv4wuqhql89rsmz8gxhwna4r2s48vww94vyvw9xllydqfygc8890qhhxa2sr3p70p3rdkgt7xuulh66uarnd3l0e0wl2ld7hw4klalacw6yk0u29g0eqx2vsvz29krw9s5n8vfckazhmx4f7393lxwp8aje47j9fpnvlgqr9p990qrmhx9vk8pvfc70wec3fn2c7sz9mttpzv74084pzcmrycqwd5c6qv95ks8duxv325yay48xs9zlgtf9d0zleneemhwzwknsct7ea7quj00359urmuvsvrftvht9wmhtkdzwe6jr6jqvjyn8ew8artcme97smx5dxy4m8yug67xcpfz8chtx0t7eerce7gtpfdn0cryx4s2erhedxk883jykck9ryj3akv7pqrvyldy3ruckgpcm9g6w6fc75yt9g466wemkhftx7tp6uskcvjnvrpn6wzadp44qmua3c23c3pylpdcx0wsv5vl3rspn36zwuzmzpma9ndpppa4dluqag8kfw7xj055szhrf4lsyquxmxq2efp74y75e535y3mgvhqgultm2f7m33hc6vk8ztymz59efth64msyqkmqx5mshm42kqwhqvznkw0ezmh22lfcd6fsh0l4gdujnmz7yfvyfdajkx80j87zmz2nhnv50qdpqjkrhem9ankxw3f06yhc6m5ltfeyhm7nq98glcgtljwss2r7m0gl8d8p2hlesa6cm0ld2y8s7prhz8gywl20dh89ve7qknljygdd5w7l5ueykmz736atgg5vevludsdut9xamwmtsye0fca6c2tl0ne8wpnsdljttt97qrf0mxemdm90v44v9wqet0utf4x0ahqqrlhf647rytaesj6j7dzqpan03za3lkqfcx7pymngzwl29rm62yklh3p884e5hz6qdwfaz98lsq9lke5ntmg2w55xvraleegkn6nftdr2ztgs58zfndpzafqs6v7tcm75hapw6hptzqwnpfwcvw38ghru55y003xm76tsd2fe6565fv5snakw74act2k2lsfg8ntaxf62ksgusdt9a6pw7mfypv2n2y9phddpj62yg93fxyqcujxw7vjced4eteendff28nmwmr3mtclyqhrry8palcsekavj8dstmkgezw6l3vq98p254mkxxye2uumaw8zh2mzvuqsgn0jfkymq76rlvx2d8e2xe6tv34vtpr09lhlehh4cwl48mjq7h0pnwlkrxyf0k0scw3szrc6wqg4hnc9whpx3whmdd2neme9j8lzauzyq45fqks6qt5vmq7lqx0a0flurpleyaq5466dzajma5vlqlgaggxxs3r3glumrpqtu6pd5mnemnuuc6f4gdjr65jdy3em8whcxwjnex6smkrxv5kjdag7cx0j8m8cg26hkkwyra9a0xqauzu0vaxd5qnx6cpm0w68evt4v960axzzuaevkagsyft9df6tnq0g2yqm7w7frht8wsxy4s0p227psd92d3vd5t45zesrvny4lvfvkn0cnwyf7p60gtx3er45xs4u4zy2ntrkx64elmp8k4v6kv0w8sh76ychxn384m4hhrrg523ex6ux0fhs63fkk7r68p3jlm4wcmxvxt872gg930m30l5v9vw6g4txy84w2wvvh7vxdu7tq50we9yp7x0wv2f6kfe4dthcmp2sjxf5l2myhegj3u8uz0m652flmsdyu57f8ncszjtkzh44afw4quw4j7dx6m322p6q2nkcw2x0n5lxwr3u2qd7t2rc28c4wgzdfgl2qvqpf95z0uv5m7p9crhl2hjzje3zqgyzgxxd4zku3yuhmj4saqeff78r78fth39p6mryyk95m4r76x30etzf7mcaudthhzrw3ae2fts576kh0c5ksnnzamtyr8ak6t4dn86a5zupn4kv426wwy7j688aasxupw7nu9qvkagm2a44ssk88ffyjxznrjtdln45vejx5ghaewzju6qze507shwtmu8evxcxv7h4axwqyvufxrvsmw3n88600af973r3k3nn3crs063j7ncc36luckfgajmqu6qtxt5emyzzmfy4pp9u4swfqtacaqgqmfjmmzansw9qv7zmhzz0wzllcv8a82f6apyt5kgrkdxg58a854rc4940gq2wy6y8lwtrkp3uf9fgms64d5d6990jzrfcr7xdkwp3fh8p66q7mfu03wpk0jzulqnu7dt6qppal3gkxhk384dvh8makve69vht6lcn032f2pavs0x4uq94s2lycmuvrevv6jrf76c90e6juz0q5w3744me7xagrunr3qpg4p8pqmyae4d7gzz8wr2znqg8wp32n2zdegz3qsmct9rhc4w5ne97epn5xdzzfa3rnqqllfqdu2672pk9a5uqldewz3v5haxnrxdhl3h52srthlv3c8ythj4m692rp74mzl2wx3svw864weq8437gqq9ejkhmkqnpzwzq7mtgp6c9r6sw2qqz4u2688wqet3yxf8rdqe0l9r9glhl5jq4arrx5f45k6l79mn9x44mmersqcrk3kmyfnptqe023rk5349a878n6qymd36tp6pvpxyxnuksyvw6yetyk4kvth6yqx5ke0q2v5ka49ewh787pgz4cnsvc2plyjwky8nurldynf44e9h0vaeukdk7xhs3slfydmmy2y84lez9uwqkj76e68fsws4g4jjlck902hs6ymmuhw52th2e82myf77wcxph7ka75qhhd4x35gd2lz8rajhjnfnns65gp3kqmwmq52st273jx7xs0xpper2s0jawgs38s3x8ggn3nk7a8k3dwlr7hry38xgyyjpvm6qlwvdyv5sau6a0rdyumrmut6uuxk90jqm2s4mp9u5rnyasedzeugegcygj72u29t7t2swvdr4mwrynryusp24d4s3l8ppj7tpks2nj8a3tlwzqh2feew6swzkf839lczs5rq4pcvmsgcy5ck5x0p759vwzqxwn7trtg0x7grfzpdc50x8zudrwad7fye8ca2zc7f8m689e34u003wc5dzs32cd8mxljkdpt4elasxcxse08948zeq239k8c442yffxz85uyqzcjyc86rfw3g79x5h3zkjq35t9v8vwskawag2vzmjtrmn4knst75kf3pfgt3mnkavs3fgyq9nfut343nmne8cct4uhj8zp0hrplpwf65kjvw8gqwstyg0gqejy4aur5".to_string(), Network::Alpha).unwrap(), NeptuneCoins::new(42)), ] } + pub fn premine_utxos() -> Vec { + let mut utxos = vec![]; + for (receiving_address, amount) in Self::premine_distribution() { + // 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)); + utxos.push(utxo); + } + utxos + } + pub fn new(header: BlockHeader, body: BlockBody, proof: Option) -> Self { Self { kernel: BlockKernel { body, header }, @@ -220,7 +243,7 @@ impl Block { pub fn accumulate_transaction( &mut self, transaction: Transaction, - old_mutator_set_accumulator: &MutatorSetAccumulator, + previous_mutator_set_accumulator: &MutatorSetAccumulator, ) { // merge transactions let merged_timestamp = BFieldElement::new(max( @@ -239,23 +262,15 @@ impl Block { // accumulate mutator set updates // Can't use the current mutator sat accumulator because it is in an in-between state. - let mut new_mutator_set_accumulator = old_mutator_set_accumulator.clone(); + let mut new_mutator_set_accumulator = previous_mutator_set_accumulator.clone(); let mutator_set_update = MutatorSetUpdate::new( - [ - self.kernel.body.transaction.kernel.inputs.clone(), - transaction.kernel.inputs, - ] - .concat(), - [ - self.kernel.body.transaction.kernel.outputs.clone(), - transaction.kernel.outputs.clone(), - ] - .concat(), + new_transaction.kernel.inputs.clone(), + new_transaction.kernel.outputs.clone(), ); // Apply the mutator set update to get the `next_mutator_set_accumulator` mutator_set_update - .apply(&mut new_mutator_set_accumulator) + .apply_to_accumulator(&mut new_mutator_set_accumulator) .expect("Mutator set mutation must work"); let block_body: BlockBody = BlockBody { @@ -285,7 +300,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) -> bool { + pub(crate) fn is_valid(&self, previous_block: &Block, now: Duration) -> 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(); @@ -311,7 +326,11 @@ impl Block { // 0.a) Block height is previous plus one if previous_block.kernel.header.height.next() != block_copy.kernel.header.height { - warn!("Height does not match previous height"); + warn!( + "Block height ({}) does not match previous height plus one ({})", + block_copy.kernel.header.height, + previous_block.kernel.header.height.next() + ); return false; } @@ -329,11 +348,16 @@ impl Block { return false; } - // 0.d) Block timestamp is greater than that of previuos 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() + > block_copy.kernel.header.timestamp.value() { - warn!("Block does not have greater timestamp than that of previous block"); + 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() + ); return false; } @@ -346,11 +370,11 @@ impl Block { } // 0.f) Block timestamp is less than host-time (utc) + 2 hours. - let future_limit = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - + Duration::from_secs(60 * 60 * 2); - if (block_copy.kernel.header.timestamp.value() as u128) >= future_limit.as_millis() { + // (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() + { warn!("block time is too far in the future"); return false; } @@ -396,7 +420,7 @@ impl Block { block_copy.kernel.body.transaction.kernel.outputs.clone(), ); let mut ms = previous_block.kernel.body.mutator_set_accumulator.clone(); - let ms_update_result = mutator_set_update.apply(&mut ms); + let ms_update_result = mutator_set_update.apply_to_accumulator(&mut ms); match ms_update_result { Ok(()) => (), Err(err) => { @@ -420,7 +444,11 @@ impl Block { if block_copy.kernel.body.transaction.kernel.timestamp.value() > block_copy.kernel.header.timestamp.value() { - warn!("Transaction with invalid timestamp found"); + warn!( + "Transaction timestamp ({}) is is larger than that of block ({})", + block_copy.kernel.body.transaction.kernel.timestamp.value(), + block_copy.kernel.header.timestamp.value() + ); return false; } @@ -526,6 +554,7 @@ impl Block { #[cfg(test)] mod block_tests { + use crate::{ config_models::network::Network, models::{ @@ -551,10 +580,12 @@ mod block_tests { #[traced_test] #[tokio::test] async fn merge_transaction_test() { + 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 global_state_lock = get_mock_global_state(network, 2, None).await; + let global_state_lock = + get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; let spending_key = global_state_lock .lock_guard() .await @@ -568,9 +599,11 @@ mod block_tests { .to_address(); let genesis_block = Block::genesis_block(); - let (mut block_1, _, _) = make_mock_block(&genesis_block, None, address); + 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); assert!( - block_1.is_valid(&genesis_block), + block_1.is_valid(&genesis_block, now), "Block 1 must be valid with only coinbase output" ); @@ -585,14 +618,18 @@ mod block_tests { let new_tx = global_state_lock .lock_guard_mut() .await - .create_transaction(vec![reciever_data], NeptuneCoins::new(1)) + .create_transaction( + vec![reciever_data], + NeptuneCoins::new(1), + now + seven_months, + ) .await .unwrap(); assert!(new_tx.is_valid(), "Created tx must be valid"); block_1.accumulate_transaction(new_tx, &genesis_block.kernel.body.mutator_set_accumulator); assert!( - block_1.is_valid(&genesis_block), + block_1.is_valid(&genesis_block, now + seven_months), "Block 1 must be valid after adding a transaction; previous mutator set hash: {} and next mutator set hash: {}", genesis_block.kernel .body @@ -651,62 +688,62 @@ mod block_tests { #[test] fn block_with_wrong_mmra_is_invalid() { + let mut rng = thread_rng(); let genesis_block = Block::genesis_block(); let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (mut block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); + 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()); - assert!(!block_1.is_valid(&genesis_block)); + assert!(!block_1.is_valid(&genesis_block, timestamp)); } #[traced_test] #[test] fn block_with_far_future_timestamp_is_invalid() { + let mut rng = thread_rng(); let genesis_block = Block::genesis_block(); + let mut now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (mut block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); + 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 = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - + Duration::from_secs(60 * 60); + let future_time1 = now + Duration::from_secs(60 * 60); block_1.kernel.header.timestamp = BFieldElement::new(future_time1.as_millis().try_into().unwrap()); - assert!(block_1.is_valid(&genesis_block)); + assert!(block_1.is_valid(&genesis_block, now)); - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap(); + now = Duration::from_millis(block_1.kernel.header.timestamp.value()); // 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()); - assert!(block_1.is_valid(&genesis_block)); + 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()); - assert!(!block_1.is_valid(&genesis_block)); + 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()); - assert!(!block_1.is_valid(&genesis_block)); + assert!(!block_1.is_valid(&genesis_block, now)); } #[test] fn can_prove_block_ancestry() { + let mut rng = thread_rng(); let genesis_block = Block::genesis_block(); let mut blocks = vec![]; blocks.push(genesis_block.clone()); @@ -722,7 +759,7 @@ mod block_tests { let wallet_secret = WalletSecret::new_random(); let recipient_address = wallet_secret.nth_generation_spending_key(0).to_address(); let (new_block, _, _) = - make_mock_block(blocks.last().unwrap(), None, recipient_address); + make_mock_block(blocks.last().unwrap(), None, recipient_address, rng.gen()); if i != 54 { ammr.append(new_block.hash()); mmra.append(new_block.hash()); diff --git a/src/models/blockchain/block/mutator_set_update.rs b/src/models/blockchain/block/mutator_set_update.rs index ccaa376a3..b54f040ce 100644 --- a/src/models/blockchain/block/mutator_set_update.rs +++ b/src/models/blockchain/block/mutator_set_update.rs @@ -23,28 +23,44 @@ impl MutatorSetUpdate { } } - /// Apply a mutator set update to a mutator set accumulator. Changes the input mutator set - /// accumulator according to the provided additions and removals. - pub fn apply(&self, ms_accumulator: &mut MutatorSetAccumulator) -> Result<()> { - let mut addition_records: Vec = self.additions.clone(); - addition_records.reverse(); - let mut removal_records = self.removals.clone(); - removal_records.reverse(); - let mut removal_records: Vec<&mut RemovalRecord> = - removal_records.iter_mut().collect::>(); - while let Some(addition_record) = addition_records.pop() { + /// Apply a mutator-set-update to a mutator-set-accumulator. Changes the mutator + /// set accumulator according to the provided addition and removal records. + pub fn apply_to_accumulator(&self, ms_accumulator: &mut MutatorSetAccumulator) -> Result<()> { + self.apply_to_accumulator_and_records(ms_accumulator, &mut []) + } + + /// Apply a mutator-set-update to a mutator-set-accumulator and a bunch of + /// removal records. Changes the mutator set accumulator according to the + /// to-be-applied addition and removal records. This method assumes that the + /// removal records in the update are distinct from the ones that are to be + /// updated. + pub fn apply_to_accumulator_and_records( + &self, + ms_accumulator: &mut MutatorSetAccumulator, + removal_records: &mut [&mut RemovalRecord], + ) -> Result<()> { + let mut cloned_removals = self.removals.clone(); + let mut applied_removal_records = cloned_removals.iter_mut().rev().collect::>(); + for addition_record in self.additions.iter() { RemovalRecord::batch_update_from_addition( - &mut removal_records, + &mut applied_removal_records, &mut ms_accumulator.kernel, ); - ms_accumulator.add(&addition_record); + RemovalRecord::batch_update_from_addition(removal_records, &mut ms_accumulator.kernel); + + ms_accumulator.add(addition_record); } - while let Some(removal_record) = removal_records.pop() { - RemovalRecord::batch_update_from_remove(&mut removal_records, removal_record); + while let Some(applied_removal_record) = applied_removal_records.pop() { + RemovalRecord::batch_update_from_remove( + &mut applied_removal_records, + applied_removal_record, + ); + + RemovalRecord::batch_update_from_remove(removal_records, applied_removal_record); - ms_accumulator.remove(removal_record); + ms_accumulator.remove(applied_removal_record); } Ok(()) diff --git a/src/models/blockchain/block/validity.rs b/src/models/blockchain/block/validity.rs index 262388060..6744be9b3 100644 --- a/src/models/blockchain/block/validity.rs +++ b/src/models/blockchain/block/validity.rs @@ -1,12 +1,15 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program, PublicInput}, + triton_vm::{ + instruction::LabelledInstruction, + program::{NonDeterminism, PublicInput}, + }, twenty_first::{self, shared_math::b_field_element::BFieldElement}, }; use twenty_first::shared_math::bfield_codec::BFieldCodec; -use crate::models::consensus::SecretWitness; +use crate::models::consensus::{tasm::program::ConsensusProgram, SecretWitness}; use self::{ coinbase_is_valid::CoinbaseIsValid, @@ -68,11 +71,21 @@ impl SecretWitness for PrincipalBlockValidationWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { + todo!() + } +} + +impl ConsensusProgram for PrincipalBlockValidationLogic { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { todo!() } } diff --git a/src/models/blockchain/block/validity/coinbase_is_valid.rs b/src/models/blockchain/block/validity/coinbase_is_valid.rs index 2bb6f2460..c8760a3cd 100644 --- a/src/models/blockchain/block/validity/coinbase_is_valid.rs +++ b/src/models/blockchain/block/validity/coinbase_is_valid.rs @@ -1,14 +1,17 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program, PublicInput}, + triton_vm::{ + instruction::LabelledInstruction, + program::{NonDeterminism, PublicInput}, + }, twenty_first::{self, shared_math::b_field_element::BFieldElement}, }; use twenty_first::shared_math::bfield_codec::BFieldCodec; use crate::models::{ blockchain::block::Block, - consensus::{SecretWitness, SupportedClaim}, + consensus::{tasm::program::ConsensusProgram, SecretWitness}, }; #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] @@ -21,16 +24,26 @@ impl SecretWitness for CoinbaseIsValidWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct CoinbaseIsValid { - supported_claim: SupportedClaim, + witness: CoinbaseIsValidWitness, +} + +impl ConsensusProgram for CoinbaseIsValid { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } diff --git a/src/models/blockchain/block/validity/correct_control_parameter_update.rs b/src/models/blockchain/block/validity/correct_control_parameter_update.rs index ea3a0cb42..374283462 100644 --- a/src/models/blockchain/block/validity/correct_control_parameter_update.rs +++ b/src/models/blockchain/block/validity/correct_control_parameter_update.rs @@ -1,13 +1,13 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program, PublicInput}, + triton_vm::program::{NonDeterminism, PublicInput}, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, }; use crate::models::{ blockchain::block::Block, - consensus::{SecretWitness, SupportedClaim}, + consensus::{tasm::program::ConsensusProgram, SecretWitness}, }; #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] @@ -20,16 +20,26 @@ impl SecretWitness for CorrectControlParameterUpdateWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct CorrectControlParameterUpdate { - pub supported_claim: SupportedClaim, + pub witness: CorrectControlParameterUpdateWitness, +} + +impl ConsensusProgram for CorrectControlParameterUpdate { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } diff --git a/src/models/blockchain/block/validity/correct_mmr_update.rs b/src/models/blockchain/block/validity/correct_mmr_update.rs index e73923656..5a36db72b 100644 --- a/src/models/blockchain/block/validity/correct_mmr_update.rs +++ b/src/models/blockchain/block/validity/correct_mmr_update.rs @@ -1,15 +1,15 @@ -use crate::Hash; +use crate::{models::consensus::tasm::program::ConsensusProgram, Hash}; use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program, PublicInput}, + triton_vm::program::{NonDeterminism, PublicInput}, twenty_first::{ shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, util_types::mmr::mmr_accumulator::MmrAccumulator, }, }; -use crate::models::consensus::{SecretWitness, SupportedClaim}; +use crate::models::consensus::SecretWitness; #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct CorrectMmrUpdateWitness { @@ -21,16 +21,26 @@ impl SecretWitness for CorrectMmrUpdateWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct CorrectMmrUpdate { - pub supported_claim: SupportedClaim, + pub witness: CorrectMmrUpdateWitness, +} + +impl ConsensusProgram for CorrectMmrUpdate { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } diff --git a/src/models/blockchain/block/validity/correct_mutator_set_update.rs b/src/models/blockchain/block/validity/correct_mutator_set_update.rs index a828f30fb..0fec0129e 100644 --- a/src/models/blockchain/block/validity/correct_mutator_set_update.rs +++ b/src/models/blockchain/block/validity/correct_mutator_set_update.rs @@ -1,12 +1,12 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program, PublicInput}, + triton_vm::program::{NonDeterminism, PublicInput}, twenty_first::{bfieldcodec_derive::BFieldCodec, shared_math::b_field_element::BFieldElement}, }; use crate::{ - models::consensus::{SecretWitness, SupportedClaim}, + models::consensus::{tasm::program::ConsensusProgram, SecretWitness}, util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator, }; @@ -20,16 +20,26 @@ impl SecretWitness for CorrectMutatorSetUpdateWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct CorrectMutatorSetUpdate { - pub supported_claim: SupportedClaim, + pub witness: CorrectMutatorSetUpdateWitness, +} + +impl ConsensusProgram for CorrectMutatorSetUpdate { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } diff --git a/src/models/blockchain/block/validity/mmr_membership.rs b/src/models/blockchain/block/validity/mmr_membership.rs index 612bdd0c4..7a41bc227 100644 --- a/src/models/blockchain/block/validity/mmr_membership.rs +++ b/src/models/blockchain/block/validity/mmr_membership.rs @@ -3,9 +3,9 @@ use crate::models::blockchain::block::Deserialize; use crate::models::blockchain::block::GetSize; use crate::models::blockchain::block::Serialize; use crate::models::blockchain::shared::Hash; +use crate::models::consensus::tasm::program::ConsensusProgram; use crate::models::consensus::SecretWitness; -use crate::models::consensus::SupportedClaim; -use crate::triton_vm::program::Program; +use tasm_lib::triton_vm::instruction::LabelledInstruction; use tasm_lib::triton_vm::program::NonDeterminism; use tasm_lib::triton_vm::program::PublicInput; use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; @@ -21,16 +21,26 @@ impl SecretWitness for MmrMembershipWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct MmrMembership { - supported_claim: SupportedClaim, + witness: MmrMembershipWitness, +} + +impl ConsensusProgram for MmrMembership { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } diff --git a/src/models/blockchain/block/validity/predecessor_is_valid.rs b/src/models/blockchain/block/validity/predecessor_is_valid.rs index a0b71a5f6..55d73f10b 100644 --- a/src/models/blockchain/block/validity/predecessor_is_valid.rs +++ b/src/models/blockchain/block/validity/predecessor_is_valid.rs @@ -1,13 +1,16 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program, PublicInput}, + triton_vm::{ + instruction::LabelledInstruction, + program::{NonDeterminism, PublicInput}, + }, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, }; use crate::models::{ blockchain::block::Block, - consensus::{SecretWitness, SupportedClaim}, + consensus::{tasm::program::ConsensusProgram, SecretWitness}, }; #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] @@ -20,16 +23,26 @@ impl SecretWitness for PredecessorIsValidWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct PredecessorIsValid { - pub supported_claim: SupportedClaim, + pub witness: PredecessorIsValidWitness, +} + +impl ConsensusProgram for PredecessorIsValid { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } diff --git a/src/models/blockchain/block/validity/transaction_is_valid.rs b/src/models/blockchain/block/validity/transaction_is_valid.rs index a3ae086ab..8c8f71653 100644 --- a/src/models/blockchain/block/validity/transaction_is_valid.rs +++ b/src/models/blockchain/block/validity/transaction_is_valid.rs @@ -1,13 +1,16 @@ use get_size::GetSize; use serde::{Deserialize, Serialize}; use tasm_lib::{ - triton_vm::program::{NonDeterminism, Program, PublicInput}, + triton_vm::{ + instruction::LabelledInstruction, + program::{NonDeterminism, PublicInput}, + }, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, }; use crate::models::{ blockchain::transaction::Transaction, - consensus::{SecretWitness, SupportedClaim}, + consensus::{tasm::program::ConsensusProgram, SecretWitness}, }; #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] @@ -20,16 +23,26 @@ impl SecretWitness for TransactionIsValidWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> tasm_lib::prelude::triton_vm::program::Program { todo!() } } #[derive(Debug, Clone, BFieldCodec, GetSize, PartialEq, Eq, Serialize, Deserialize)] pub struct TransactionIsValid { - supported_claim: SupportedClaim, + witness: TransactionIsValidWitness, +} + +impl ConsensusProgram for TransactionIsValid { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } diff --git a/src/models/blockchain/transaction/mod.rs b/src/models/blockchain/transaction/mod.rs index d47a7a35b..2febdc513 100644 --- a/src/models/blockchain/transaction/mod.rs +++ b/src/models/blockchain/transaction/mod.rs @@ -1,4 +1,6 @@ +use crate::models::blockchain::block::mutator_set_update::MutatorSetUpdate; use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::{ValidityTree, WitnessType}; use crate::prelude::{triton_vm, twenty_first}; pub mod primitive_witness; @@ -6,8 +8,7 @@ pub mod transaction_kernel; pub mod utxo; pub mod validity; -use crate::models::consensus::Witness; -use anyhow::Result; +use anyhow::{bail, Result}; use arbitrary::Arbitrary; use get_size::GetSize; use itertools::Itertools; @@ -38,8 +39,6 @@ use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulat use crate::util_types::mutator_set::mutator_set_trait::MutatorSet; use crate::util_types::mutator_set::removal_record::RemovalRecord; -pub type TransactionWitness = Witness; - #[derive( Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec, Default, Arbitrary, )] @@ -57,7 +56,8 @@ impl PublicAnnouncement { pub struct Transaction { pub kernel: TransactionKernel, - pub witness: TransactionWitness, + #[bfield_codec(ignore)] + pub witness: TransactionValidationLogic, } /// Make `Transaction` hashable with `StdHash` for using it in `HashMap`. @@ -72,25 +72,26 @@ impl StdHash for Transaction { } impl Transaction { - /// Update mutator set data in a transaction to update its - /// compatibility with a new block. Note that for SingleProof witnesses, this will - /// invalidate the proof, requiring an update. For LinkedProofs or PrimitiveWitness - /// witnesses the witness data can be and is updated. - pub fn update_mutator_set_records( - &mut self, - previous_mutator_set_accumulator: &MutatorSetAccumulator, + /// Create a new `Transaction`` from a `PrimitiveWitness` (which defines an old + /// `Transaction`) by updating the mutator set records according to a new + /// `Block`. + fn new_with_updated_mutator_set_records_given_primitive_witness( + old_primitive_witness: &PrimitiveWitness, block: &Block, - ) -> Result<()> { - let mut msa_state: MutatorSetAccumulator = previous_mutator_set_accumulator.clone(); + ) -> Result { + let mut msa_state: MutatorSetAccumulator = + old_primitive_witness.mutator_set_accumulator.clone(); let block_addition_records: Vec = block.kernel.body.transaction.kernel.outputs.clone(); - let mut transaction_removal_records: Vec = self.kernel.inputs.clone(); + let mut transaction_removal_records: Vec = + old_primitive_witness.kernel.inputs.clone(); let mut transaction_removal_records: Vec<&mut RemovalRecord> = transaction_removal_records.iter_mut().collect(); let mut block_removal_records = block.kernel.body.transaction.kernel.inputs.clone(); block_removal_records.reverse(); let mut block_removal_records: Vec<&mut RemovalRecord> = block_removal_records.iter_mut().collect::>(); + let mut primitive_witness = old_primitive_witness.clone(); // Apply all addition records in the block for block_addition_record in block_addition_records { @@ -107,23 +108,23 @@ impl Transaction { ); // Batch update primitive witness membership proofs - if let Witness::Primitive(witness) = &mut self.witness { - let membership_proofs = - &mut witness.input_membership_proofs.iter_mut().collect_vec(); - let own_items = witness - .input_utxos - .utxos - .iter() - .map(Hash::hash) - .collect_vec(); - MsMembershipProof::batch_update_from_addition( - membership_proofs, - &own_items, - &msa_state.kernel, - &block_addition_record, - ) - .expect("MS MP update from add must succeed in wallet handler"); - } + let membership_proofs = &mut primitive_witness + .input_membership_proofs + .iter_mut() + .collect_vec(); + let own_items = primitive_witness + .input_utxos + .utxos + .iter() + .map(Hash::hash) + .collect_vec(); + MsMembershipProof::batch_update_from_addition( + membership_proofs, + &own_items, + &msa_state.kernel, + &block_addition_record, + ) + .expect("MS MP update from add must succeed in wallet handler"); msa_state.add(&block_addition_record); } @@ -140,11 +141,14 @@ impl Transaction { ); // Batch update primitive witness membership proofs - if let Witness::Primitive(witness) = &mut self.witness { - let membership_proofs = - &mut witness.input_membership_proofs.iter_mut().collect_vec(); + let membership_proofs = &mut primitive_witness + .input_membership_proofs + .iter_mut() + .collect_vec(); + if let Err(e) = MsMembershipProof::batch_update_from_remove(membership_proofs, removal_record) - .expect("MS MP update from remove must succeed in wallet handler"); + { + bail!("`MsMembershipProof::batch_update_from_remove` must work when updating mutator set records on transaction. Got error: {}", e); } msa_state.remove(removal_record); @@ -158,47 +162,142 @@ impl Transaction { "Internal MSA state must match that from block" ); - // Write all transaction's membership proofs and removal records back - for (tx_input, new_rr) in self - .kernel - .inputs - .iter_mut() - .zip_eq(transaction_removal_records.into_iter()) - { - *tx_input = new_rr.to_owned(); - } + let kernel = primitive_witness.kernel.clone(); + let witness = TransactionValidationLogic::from(primitive_witness); + Ok(Transaction { kernel, witness }) + } - // Update the claimed mutator set hash - self.kernel.mutator_set_hash = block_msa_hash; + /// Create a new `Transaction` by updating the given one with the mutator set + /// update contained in the `Block`. No primitive witness is present, instead + /// a proof (or faith witness) is given. So: + /// 1. Verify the proof + /// 2. Update the records + /// 3. Prove correctness of 1 and 2 + /// 4. Use resulting proof as new witness. + fn new_with_updated_mutator_set_records_given_proof( + old_transaction: &Transaction, + previous_mutator_set_accumulator: &MutatorSetAccumulator, + block: &Block, + ) -> Result { + let block_addition_records = block.kernel.body.transaction.kernel.outputs.clone(); + let block_removal_records = block.kernel.body.transaction.kernel.inputs.clone(); + let mutator_set_update = + MutatorSetUpdate::new(block_removal_records, block_addition_records); + + // apply mutator set update to get new mutator set accumulator + let mut new_mutator_set_accumulator = previous_mutator_set_accumulator.clone(); + let mut new_inputs = old_transaction.kernel.inputs.clone(); + mutator_set_update + .apply_to_accumulator_and_records( + &mut new_mutator_set_accumulator, + &mut new_inputs.iter_mut().collect_vec(), + ) + .unwrap_or_else(|_| panic!("Could not apply mutator set update.")); - Ok(()) + // Sanity check of block validity + let msa_hash = new_mutator_set_accumulator.hash(); + assert_eq!( + block.kernel.body.mutator_set_accumulator.hash(), + msa_hash, + "Internal MSA state must match that from block" + ); + + // compute new kernel + let mut new_kernel = old_transaction.kernel.clone(); + new_kernel.inputs = new_inputs; + new_kernel.mutator_set_hash = msa_hash; + + // compute updated witness through recursion + let validation_tree = TransactionValidationLogic::validation_tree_from_mutator_set_update( + &old_transaction.witness.vast, + &old_transaction.kernel, + previous_mutator_set_accumulator, + &new_kernel, + &mutator_set_update, + ); + + Ok(Transaction { + kernel: new_kernel, + witness: TransactionValidationLogic::new(validation_tree, None), + }) + } + + /// Update mutator set data in a transaction to update its + /// compatibility with a new block. Note that for Proof witnesses, this will + /// invalidate the proof, requiring an update. + pub fn new_with_updated_mutator_set_records( + &self, + previous_mutator_set_accumulator: &MutatorSetAccumulator, + block: &Block, + ) -> Result { + if let Some(primitive_witness) = &self.witness.maybe_primitive_witness { + Self::new_with_updated_mutator_set_records_given_primitive_witness( + primitive_witness, + block, + ) + } else { + Self::new_with_updated_mutator_set_records_given_proof( + self, + previous_mutator_set_accumulator, + block, + ) + } } pub fn get_timestamp(&self) -> Result { Ok(std::time::UNIX_EPOCH + std::time::Duration::from_millis(self.kernel.timestamp.value())) } - /// Validate Transaction - /// - /// This method tests the transaction's internal consistency in - /// isolation, without the context of the canonical chain. + /// 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. pub fn is_valid(&self) -> bool { - match &self.witness { - Witness::ValidationLogic(validity_logic) => validity_logic.verify(), - Witness::Primitive(primitive_witness) => { - warn!("Verifying transaction by raw witness; unlock key might be exposed!"); - self.validate_primitive_witness(primitive_witness) - } - Witness::SingleProof(_) => true, - // TODO: All validation of `Faith` should panic as only the genesis block - // should have a Faith witness once all validation logic is implemented. - Witness::Faith => true, + let kernel_hash = self.kernel.mast_hash(); + self.witness.vast.verify(kernel_hash) + } + + fn merge_primitive_witnesses( + self_witness: PrimitiveWitness, + other_witness: PrimitiveWitness, + merged_kernel: &TransactionKernel, + ) -> PrimitiveWitness { + PrimitiveWitness { + input_utxos: self_witness + .input_utxos + .cat(other_witness.input_utxos.clone()), + input_lock_scripts: [ + self_witness.input_lock_scripts.clone(), + other_witness.input_lock_scripts.clone(), + ] + .concat(), + type_scripts: self_witness + .type_scripts + .iter() + .cloned() + .chain(other_witness.type_scripts.iter().cloned()) + .unique() + .collect_vec(), + lock_script_witnesses: [ + self_witness.lock_script_witnesses.clone(), + other_witness.lock_script_witnesses.clone(), + ] + .concat(), + input_membership_proofs: [ + self_witness.input_membership_proofs.clone(), + other_witness.input_membership_proofs.clone(), + ] + .concat(), + output_utxos: self_witness + .output_utxos + .cat(other_witness.output_utxos.clone()), + mutator_set_accumulator: self_witness.mutator_set_accumulator.clone(), + kernel: merged_kernel.clone(), } } - /// Merge two transactions. Both input transactions must have a - /// valid SingleProof witness for this operation to work. - /// The mutator sets are assumed to be identical; this is the responsibility of the caller. + /// Merge two transactions. Both input transactions must have a valid + /// Proof witness for this operation to work. The mutator sets are + /// assumed to be identical; this is the responsibility of the caller. pub fn merge_with(self, other: Transaction) -> Transaction { assert_eq!( self.kernel.mutator_set_hash, other.kernel.mutator_set_hash, @@ -221,11 +320,11 @@ impl Transaction { }; let merged_kernel = TransactionKernel { - inputs: [self.kernel.inputs, other.kernel.inputs].concat(), - outputs: [self.kernel.outputs, other.kernel.outputs].concat(), + inputs: [self.kernel.inputs.clone(), other.kernel.inputs.clone()].concat(), + outputs: [self.kernel.outputs.clone(), other.kernel.outputs.clone()].concat(), public_announcements: [ - self.kernel.public_announcements, - other.kernel.public_announcements, + self.kernel.public_announcements.clone(), + other.kernel.public_announcements.clone(), ] .concat(), fee: self.kernel.fee + other.kernel.fee, @@ -234,61 +333,63 @@ impl Transaction { mutator_set_hash: self.kernel.mutator_set_hash, }; - let merged_witness = match (&self.witness, &other.witness) { - (Witness::Primitive(self_witness), Witness::Primitive(other_witness)) => { - if self_witness.kernel.mutator_set_hash != other_witness.kernel.mutator_set_hash { - error!("Cannot merge two transactions with distinct mutator set hashes."); + let (merged_witness, maybe_primitive_witness) = match ( + &self.witness.vast.witness_type, + &other.witness.vast.witness_type, + ) { + (WitnessType::Decomposition, WitnessType::Decomposition) => { + if self.witness.maybe_primitive_witness.is_some() + && other.witness.maybe_primitive_witness.is_some() + { + let self_witness = self.witness.maybe_primitive_witness.unwrap(); + let other_witness = other.witness.maybe_primitive_witness.unwrap(); + let primitive_witness = Self::merge_primitive_witnesses( + self_witness, + other_witness, + &merged_kernel, + ); + let vast = TransactionValidationLogic::validation_tree_from_primitive_witness( + primitive_witness.clone(), + ); + (vast, Some(primitive_witness)) + } else { + error!("Cannot merge two unproven transactions when primitive witnesses are not both present."); + return self.clone(); } - Witness::Primitive(PrimitiveWitness { - input_utxos: self_witness - .input_utxos - .cat(other_witness.input_utxos.clone()), - input_lock_scripts: [ - self_witness.input_lock_scripts.clone(), - other_witness.input_lock_scripts.clone(), - ] - .concat(), - type_scripts: self_witness - .type_scripts - .iter() - .cloned() - .chain(other_witness.type_scripts.iter().cloned()) - .unique() - .collect_vec(), - lock_script_witnesses: [ - self_witness.lock_script_witnesses.clone(), - other_witness.lock_script_witnesses.clone(), - ] - .concat(), - input_membership_proofs: [ - self_witness.input_membership_proofs.clone(), - other_witness.input_membership_proofs.clone(), - ] - .concat(), - output_utxos: self_witness - .output_utxos - .cat(other_witness.output_utxos.clone()), - mutator_set_accumulator: self_witness.mutator_set_accumulator.clone(), - kernel: merged_kernel.clone(), - }) } // TODO: Merge with recursion - (Witness::ValidationLogic(_self_vl), Witness::ValidationLogic(_other_vl)) => { - Witness::Faith + (WitnessType::Proof(_own_proof), WitnessType::Proof(_other_proof)) => { + // 1. verify proof 1 + // 2. verify proof 2 + // 3. prove correctness of steps 1 and 2 + // 4. use resulting proof as new witness + let vast = TransactionValidationLogic::validation_tree_from_merger( + &self.kernel, + &self.witness.vast, + &other.kernel, + &other.witness.vast, + &merged_kernel, + ); + (vast, None) } - (Witness::Faith, _) => Witness::Faith, - (_, Witness::Faith) => Witness::Faith, - _ => { - let self_type = std::mem::discriminant(&self.witness); - let other_type = std::mem::discriminant(&other.witness); - todo!("Can only merge primitive witnesses for now. Got: self: {self_type:?}; other: {other_type:?}"); + (WitnessType::Faith, _) => (ValidityTree::axiom(), None), + (_, WitnessType::Faith) => (ValidityTree::axiom(), None), + (a, b) => { + todo!( + "Can only merge primitive witnesses for now. WitnessTypes were {:?} and {:?}", + a, + b + ); } }; Transaction { kernel: merged_kernel, - witness: merged_witness, + witness: TransactionValidationLogic { + vast: merged_witness, + maybe_primitive_witness, + }, } } @@ -317,7 +418,7 @@ impl Transaction { /// Verify the transaction directly from the primitive witness, without proofs or /// decomposing into subclaims. - fn validate_primitive_witness(&self, primitive_witness: &PrimitiveWitness) -> bool { + pub fn validate_primitive_witness(&self, primitive_witness: &PrimitiveWitness) -> bool { // verify lock scripts for (lock_script, secret_input) in primitive_witness .input_lock_scripts diff --git a/src/models/blockchain/transaction/primitive_witness.rs b/src/models/blockchain/transaction/primitive_witness.rs index 3791be530..664f9fb1d 100644 --- a/src/models/blockchain/transaction/primitive_witness.rs +++ b/src/models/blockchain/transaction/primitive_witness.rs @@ -324,7 +324,7 @@ pub(crate) fn arbitrary_primitive_witness_with( let input_membership_proofs = msa_and_records.membership_proofs; let input_removal_records = msa_and_records.removal_records; - let type_scripts = vec![TypeScript::new(NativeCurrency::program())]; + let type_scripts = vec![TypeScript::new(NativeCurrency.program())]; // prepare to unwrap let input_utxos = input_utxos.clone(); @@ -400,12 +400,13 @@ mod test { transaction::validity::TransactionValidationLogic, type_scripts::neptune_coins::NeptuneCoins, }; + use crate::models::consensus::mast_hash::MastHash; use proptest::collection::vec; use proptest::prop_assert; use proptest_arbitrary_interop::arb; use test_strategy::proptest; - #[proptest(cases = 1)] + #[proptest(cases = 5)] fn arbitrary_transaction_is_valid( #[strategy(1usize..3)] _num_inputs: usize, #[strategy(1usize..3)] _num_outputs: usize, @@ -413,10 +414,12 @@ mod test { #[strategy(PrimitiveWitness::arbitrary_with((#_num_inputs, #_num_outputs, #_num_public_announcements)))] transaction_primitive_witness: PrimitiveWitness, ) { - prop_assert!(TransactionValidationLogic::new_from_primitive_witness( - &transaction_primitive_witness - ) - .verify()); + let kernel_hash = transaction_primitive_witness.kernel.mast_hash(); + prop_assert!( + TransactionValidationLogic::from(transaction_primitive_witness) + .vast + .verify(kernel_hash) + ); } #[proptest] diff --git a/src/models/blockchain/transaction/transaction_kernel.rs b/src/models/blockchain/transaction/transaction_kernel.rs index 41050b3b0..025128ed3 100644 --- a/src/models/blockchain/transaction/transaction_kernel.rs +++ b/src/models/blockchain/transaction/transaction_kernel.rs @@ -48,11 +48,10 @@ pub struct TransactionKernel { pub mutator_set_hash: Digest, } -impl TransactionKernel { - pub(crate) fn from_primitive_witness( - transaction_primitive_witness: &PrimitiveWitness, - ) -> TransactionKernel { - transaction_primitive_witness.kernel.clone() + +impl From for TransactionKernel { + fn from(transaction_primitive_witness: PrimitiveWitness) -> Self { + transaction_primitive_witness.kernel } } diff --git a/src/models/blockchain/transaction/utxo.rs b/src/models/blockchain/transaction/utxo.rs index cf7e92d11..72e8ddc57 100644 --- a/src/models/blockchain/transaction/utxo.rs +++ b/src/models/blockchain/transaction/utxo.rs @@ -1,4 +1,5 @@ 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::prelude::{triton_vm, twenty_first}; @@ -11,6 +12,7 @@ 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; @@ -66,22 +68,111 @@ impl Utxo { Self::new( lock_script, vec![Coin { - type_script_hash: NativeCurrency::hash(), + type_script_hash: NativeCurrency.hash(), state: amount.encode(), }], ) } - pub fn get_native_coin_amount(&self) -> NeptuneCoins { + /// Get the amount of Neptune coins that are encapsulated in this UTXO, + /// regardless of which other coins are present. (Even if that makes the + /// Neptune coins unspendable.) + pub fn get_native_currency_amount(&self) -> NeptuneCoins { self.coins .iter() - .filter(|coin| coin.type_script_hash == NativeCurrency::hash()) + .filter(|coin| coin.type_script_hash == NativeCurrency.hash()) .map(|coin| match NeptuneCoins::decode(&coin.state) { Ok(boxed_amount) => *boxed_amount, Err(_) => NeptuneCoins::zero(), }) .sum() } + + /// If the UTXO has a timelock, find out what the release date is. + 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) + } + + /// Determine whether the UTXO has coins that contain only known type + /// scripts. If other type scripts are included, then we cannot spend + /// this UTXO. + pub fn has_known_type_scripts(&self) -> bool { + let known_type_script_hashes = [NativeCurrency.hash(), TimeLock.hash()]; + if !self + .coins + .iter() + .all(|c| known_type_script_hashes.contains(&c.type_script_hash)) + { + return false; + } + true + } + + /// Determine if the UTXO can be spent at a given date in the future, + /// 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 { + // unknown type script + if !self.has_known_type_scripts() { + return false; + } + + // decode and test release date(s) (if any) + for state in self + .coins + .iter() + .filter(|c| c.type_script_hash == TimeLock.hash()) + .map(|c| c.state.clone()) + { + match BFieldElement::decode(&state) { + Ok(release_date) => { + if timestamp <= release_date.value() { + return false; + } + } + Err(_) => { + return false; + } + }; + } + + true + } + + /// 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 { + if !self.has_known_type_scripts() { + return false; + } + + // decode and test release date(s) (if any) + let mut have_future_release_date = false; + for state in self + .coins + .iter() + .filter(|c| c.type_script_hash == TimeLock.hash()) + .map(|c| c.state.clone()) + { + match BFieldElement::decode(&state) { + Ok(release_date) => { + if timestamp <= release_date.value() { + have_future_release_date = true; + } + } + Err(_) => { + return false; + } + }; + } + + have_future_release_date + } } /// Make `Utxo` hashable with `StdHash` for using it in `HashMap`. @@ -107,7 +198,7 @@ pub fn pseudorandom_utxo(seed: [u8; 32]) -> Utxo { impl<'a> Arbitrary<'a> for Utxo { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let lock_script_hash: Digest = Digest::arbitrary(u)?; - let type_script_hash = NativeCurrency::hash(); + let type_script_hash = NativeCurrency.hash(); let amount = NeptuneCoins::arbitrary(u)?; let coins = vec![Coin { type_script_hash, @@ -211,4 +302,25 @@ mod utxo_tests { let utxo_again: Utxo = serde_json::from_str(&serialized).unwrap(); assert_eq!(utxo, utxo_again); } + + #[test] + fn utxo_timelock_test() { + let mut rng = thread_rng(); + let release_date = rng.next_u64() >> 1; + let mut delta = release_date + 1; + while delta > release_date { + delta = rng.next_u64() >> 1; + } + let mut utxo = Utxo::new( + LockScript { + program: Program::new(&[]), + }, + NeptuneCoins::new(1).to_native_coins(), + ); + utxo.coins.push(TimeLock::until(release_date)); + assert!(!utxo.can_spend_at(release_date - delta)); + assert!(utxo.is_timelocked_but_otherwise_spendable_at(release_date - delta)); + assert!(utxo.can_spend_at(release_date + delta)); + assert!(!utxo.is_timelocked_but_otherwise_spendable_at(release_date + delta)); + } } diff --git a/src/models/blockchain/transaction/validity.rs b/src/models/blockchain/transaction/validity.rs index 6f3996580..a9541cfd5 100644 --- a/src/models/blockchain/transaction/validity.rs +++ b/src/models/blockchain/transaction/validity.rs @@ -1,3 +1,9 @@ +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::{ + ValidationLogic, ValidityAstType, ValidityTree, WhichProgram, WitnessType, +}; use crate::prelude::twenty_first; pub mod kernel_to_lock_scripts; @@ -6,88 +12,217 @@ pub mod lockscripts_halt; pub mod removal_records_integrity; pub mod tasm; pub mod typescripts_halt; +use crate::models::blockchain::transaction; +use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; +use crate::util_types::mutator_set::mutator_set_trait::MutatorSet; -use anyhow::{Ok, Result}; use get_size::GetSize; use serde::{Deserialize, Serialize}; -use tracing::info; +use tasm_lib::triton_vm::proof::Claim; +use tasm_lib::Digest; use twenty_first::shared_math::bfield_codec::BFieldCodec; -use crate::models::consensus::ValidationLogic; - use self::lockscripts_halt::LockScriptsHalt; use self::removal_records_integrity::RemovalRecordsIntegrity; use self::{ kernel_to_lock_scripts::KernelToLockScripts, kernel_to_type_scripts::KernelToTypeScripts, typescripts_halt::TypeScriptsHalt, }; -use super::PrimitiveWitness; - -/// The validity of a transaction, in the base case, decomposes into -/// these subclaims. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub struct TransactionValidationLogic { - // programs: [lock_script], input: transaction kernel mast hash, witness: secret spending key, output: [] - pub lock_scripts_halt: LockScriptsHalt, - // program: todo, input: transaction kernel mast hash, witness: input utxos, utxo mast auth path, output: hashes of lock scripts - pub kernel_to_lock_scripts: KernelToLockScripts, +use super::primitive_witness::PrimitiveWitness; +use super::transaction_kernel::TransactionKernel; +use super::Transaction; - // program: recompute swbf indices, input: transaction kernel mast hash, witness: inputs + mutator set accumulator, output: [] - pub removal_records_integrity: RemovalRecordsIntegrity, - - // program: todo, input: transaction kernel mast hash, witness: outputs + kernel mast auth path + coins, output: hashes of type scripts - pub kernel_to_typescripts: KernelToTypeScripts, +/// This boolean expression determines whether a transaction is valid. Until we start +/// proving away subexpressions, we drag around the original primitive witness that +/// generated the tree. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec, Default)] +pub struct TransactionValidationLogic { + pub vast: ValidityTree, + pub maybe_primitive_witness: Option, +} - // programs: [type script], input: transaction kernel mast hash, witness: inputs + outputs + any, output: [] - pub type_scripts_halt: TypeScriptsHalt, +pub enum TransactionValidityTreeUpdateError { + ProofIsPresent, } impl TransactionValidationLogic { - pub fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { - let lock_scripts_halt = LockScriptsHalt::new_from_primitive_witness(primitive_witness); - let kernel_to_lock_scripts = - KernelToLockScripts::new_from_primitive_witness(primitive_witness); - let removal_records_integrity = - RemovalRecordsIntegrity::new_from_primitive_witness(primitive_witness); - let kernel_to_typescripts = - KernelToTypeScripts::new_from_primitive_witness(primitive_witness); - let type_scripts_halt = TypeScriptsHalt::new_from_primitive_witness(primitive_witness); + pub fn new(tree: ValidityTree, maybe_primitive_witness: Option) -> Self { Self { - lock_scripts_halt, - kernel_to_lock_scripts, - removal_records_integrity, - kernel_to_typescripts, - type_scripts_halt, + vast: tree, + maybe_primitive_witness, } } - pub fn prove(&mut self) -> Result<()> { - self.lock_scripts_halt.prove()?; + fn new_validity_tree( + kernel_hash: Digest, + primitive: ValidityTree, + mutator_set_update: ValidityTree, + merger: ValidityTree, + ) -> ValidityTree { + ValidityTree::root( + kernel_hash, + ValidityTree::any(vec![primitive, mutator_set_update, merger]), + ) + } + + pub fn validation_tree_from_merger( + own_kernel: &TransactionKernel, + own_proof: &ValidityTree, + other_kernel: &TransactionKernel, + other_proof: &ValidityTree, + new_kernel: &TransactionKernel, + ) -> ValidityTree { + if !own_proof.verify(own_kernel.mast_hash()) + || !other_proof.verify(other_kernel.mast_hash()) + || [own_kernel.inputs.clone(), other_kernel.inputs.clone()].concat() + != new_kernel.inputs + || [own_kernel.outputs.clone(), other_kernel.outputs.clone()].concat() + != new_kernel.outputs + || [ + own_kernel.public_announcements.clone(), + other_kernel.public_announcements.clone(), + ] + .concat() + != new_kernel.public_announcements + || own_kernel.fee + other_kernel.fee != new_kernel.fee + || 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() + || own_kernel.mutator_set_hash != other_kernel.mutator_set_hash + || other_kernel.mutator_set_hash != new_kernel.mutator_set_hash + { + return ValidityTree::none(); + } - self.removal_records_integrity.prove()?; + Self::new_validity_tree( + new_kernel.mast_hash(), + ValidityTree::none(), + ValidityTree::none(), + ValidityTree::new( + ValidityAstType::Atomic(None, Claim::new(Digest::default()), WhichProgram::Merger), + WitnessType::Faith, + ), + ) + } - // not supported yet: - // self.kernel_to_lock_scripts.prove()?; - // self.kernel_to_typescripts.prove()?; - // self.type_scripts_halt.prove()?; - Ok(()) + pub fn validation_tree_from_primitive_witness( + primitive_witness: PrimitiveWitness, + ) -> ValidityTree { + let primitive = ValidityTree::all(vec![ + LockScriptsHalt::from(primitive_witness.clone()).vast(), + TypeScriptsHalt::from(primitive_witness.clone()).vast(), + RemovalRecordsIntegrity::from(primitive_witness.clone()).vast(), + ]); + Self::new_validity_tree( + primitive_witness.kernel.mast_hash(), + primitive, + ValidityTree::none(), + ValidityTree::none(), + ) } - pub fn verify(&self) -> bool { - info!("validity logic for 'kernel_to_lock_scripts', 'kernel_to_type_scripts', 'type_scripts_halt' not implemented yet."); - let lock_scripts_halt = self.lock_scripts_halt.verify(); - let removal_records_integral = self.removal_records_integrity.verify(); - if !lock_scripts_halt { - eprintln!("Lock scripts don't halt."); + pub fn validation_tree_from_mutator_set_update( + old_tree: &ValidityTree, + old_kernel: &TransactionKernel, + old_mutator_set_accumulator: &MutatorSetAccumulator, + new_kernel: &TransactionKernel, + mutator_set_update: &MutatorSetUpdate, + ) -> ValidityTree { + if !old_tree.verify(old_kernel.mast_hash()) + || old_kernel.mutator_set_hash != old_mutator_set_accumulator.hash() + { + return ValidityTree::none(); + } + + let mut mutator_set_accumulator = old_mutator_set_accumulator.clone(); + if mutator_set_update + .apply_to_accumulator(&mut mutator_set_accumulator) + .is_err() + { + return ValidityTree::none(); } - if !removal_records_integral { - eprintln!("Removal records are not integral."); + + if new_kernel.mutator_set_hash != mutator_set_accumulator.hash() { + return ValidityTree::none(); } - // && self.kernel_to_lock_scripts.verify() - // && self.kernel_to_typescripts.verify() - // && self.type_scripts_halt.verify() - lock_scripts_halt && removal_records_integral + Self::new_validity_tree( + new_kernel.mast_hash(), + ValidityTree::none(), + ValidityTree::new( + ValidityAstType::Atomic( + None, + Claim::new(Digest::default()), + WhichProgram::MutatorSetUpdate, + ), + WitnessType::Faith, + ), + ValidityTree::none(), + ) + } +} + +// impl TransactionValidationLogic { +// pub fn prove(&mut self) -> Result<()> { +// self.lock_scripts_halt.prove()?; + +// self.removal_records_integrity.prove()?; + +// // not supported yet: +// // self.kernel_to_lock_scripts.prove()?; +// // self.kernel_to_typescripts.prove()?; +// // self.type_scripts_halt.prove()?; +// Ok(()) +// } + +// pub fn verify(&self) -> bool { +// info!("validity logic for 'kernel_to_lock_scripts', 'kernel_to_type_scripts', 'type_scripts_halt' not implemented yet."); +// let lock_scripts_halt = self.lock_scripts_halt.verify(); +// let removal_records_integral = self.removal_records_integrity.verify(); +// // let type_scripts_halt = self.type_scripts_halt.verify(); +// if !lock_scripts_halt { +// eprintln!("Lock scripts don't halt (gracefully)."); +// } +// // if !type_scripts_halt { +// // eprintln!("Type scripts don't halt (gracefully)."); +// // } +// if !removal_records_integral { +// eprintln!("Removal records are not integral."); +// } +// // && self.kernel_to_lock_scripts.verify() +// // && self.kernel_to_typescripts.verify() +// lock_scripts_halt && removal_records_integral +// } +// } + +impl From for TransactionValidationLogic { + fn from(primitive_witness: transaction::PrimitiveWitness) -> Self { + let lock_scripts_halt = LockScriptsHalt::from(primitive_witness.clone()); + let _kernel_to_lock_scripts = KernelToLockScripts::from(primitive_witness.clone()); + let _removal_records_integrity = RemovalRecordsIntegrity::from(primitive_witness.clone()); + let _kernel_to_typescripts = KernelToTypeScripts::from(primitive_witness.clone()); + let type_scripts_halt = TypeScriptsHalt::from(primitive_witness.clone()); + Self { + vast: ValidityTree { + vast_type: ValidityAstType::All(vec![ + lock_scripts_halt.vast(), + // kernel_to_lock_scripts, + // removal_records_integrity, + // kernel_to_typescripts, + type_scripts_halt.vast(), + ]), + witness_type: WitnessType::Decomposition, + }, + maybe_primitive_witness: Some(primitive_witness), + } + } +} + +impl From for TransactionValidationLogic { + fn from(transaction: Transaction) -> Self { + transaction.witness } } diff --git a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs index 2a5bf85ca..002ec9990 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_lock_scripts.rs @@ -1,19 +1,19 @@ use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::tasm::program::ConsensusProgram; use crate::prelude::{triton_vm, twenty_first}; -use crate::models::blockchain::transaction::PrimitiveWitness; +use crate::models::blockchain::transaction::{self}; use crate::models::blockchain::transaction::{ transaction_kernel::TransactionKernelField, utxo::Utxo, }; -use crate::models::consensus::{ClaimSupport, SupportedClaim}; -use crate::models::consensus::{SecretWitness, ValidationLogic}; +use crate::models::consensus::SecretWitness; use get_size::GetSize; -use itertools::Itertools; use serde::{Deserialize, Serialize}; use tasm_lib::library::Library; use tasm_lib::traits::compiled_program::CompiledProgram; -use triton_vm::prelude::{BFieldElement, Claim, Digest, NonDeterminism, Program, PublicInput}; +use tasm_lib::triton_vm::instruction::LabelledInstruction; +use triton_vm::prelude::{BFieldElement, Digest, NonDeterminism, PublicInput}; use twenty_first::shared_math::bfield_codec::BFieldCodec; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] @@ -27,67 +27,41 @@ impl SecretWitness for KernelToLockScriptsWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> triton_vm::prelude::Program { todo!() } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct KernelToLockScripts { - pub supported_claim: SupportedClaim, + pub witness: KernelToLockScriptsWitness, } -impl KernelToLockScripts { - // TODO: Remove after implementing this struct - pub fn dummy() -> Self { - Self { - supported_claim: SupportedClaim::dummy(), - } +impl ConsensusProgram for KernelToLockScripts { + fn source(&self) { + todo!() } -} -impl ValidationLogic for KernelToLockScripts { - type PrimitiveWitness = PrimitiveWitness; + fn code(&self) -> Vec { + todo!() + } +} - fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { - let claim = Claim { - input: primitive_witness.kernel.mast_hash().into(), - output: primitive_witness - .input_lock_scripts - .iter() - .flat_map(|ls| ls.hash().values().to_vec()) - .collect_vec(), - // program_digest: Self::program().hash::(), - program_digest: Digest::default(), - }; - let _kernel_to_lock_scripts_witness = KernelToLockScriptsWitness { +impl From for KernelToLockScripts { + fn from(primitive_witness: transaction::PrimitiveWitness) -> Self { + let kernel_to_lock_scripts_witness = KernelToLockScriptsWitness { input_utxos: primitive_witness.input_utxos.utxos.clone(), mast_path: primitive_witness .kernel .mast_path(TransactionKernelField::InputUtxos), }; - let supported_claim = SupportedClaim { - claim, - // support: ClaimSupport::SecretWitness(kernel_to_lock_scripts_witness), - support: ClaimSupport::DummySupport, - }; - Self { supported_claim } - } - - fn validation_program(&self) -> Program { - todo!() - } - - fn support(&self) -> ClaimSupport { - self.supported_claim.support.clone() - } - - fn claim(&self) -> Claim { - self.supported_claim.claim.clone() + Self { + witness: kernel_to_lock_scripts_witness, + } } } diff --git a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs index 52eb9dd2b..35fb77c0d 100644 --- a/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs +++ b/src/models/blockchain/transaction/validity/kernel_to_type_scripts.rs @@ -1,9 +1,9 @@ use crate::models::blockchain::type_scripts::TypeScript; -use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::tasm::program::ConsensusProgram; use crate::prelude::{triton_vm, twenty_first}; -use crate::models::blockchain::transaction::PrimitiveWitness; -use crate::models::consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}; +use crate::models::blockchain::transaction::{self}; +use crate::models::consensus::SecretWitness; use get_size::GetSize; use itertools::Itertools; @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use tasm_lib::library::Library; use tasm_lib::traits::compiled_program::CompiledProgram; use triton_vm::instruction::LabelledInstruction; -use triton_vm::prelude::{BFieldElement, Claim, Digest, NonDeterminism, Program, PublicInput}; +use triton_vm::prelude::{BFieldElement, Digest, NonDeterminism, PublicInput}; use twenty_first::shared_math::bfield_codec::BFieldCodec; #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] @@ -25,32 +25,33 @@ impl SecretWitness for KernelToTypeScriptsWitness { todo!() } - fn subprogram(&self) -> Program { + fn standard_input(&self) -> PublicInput { todo!() } - fn standard_input(&self) -> PublicInput { + fn program(&self) -> triton_vm::prelude::Program { todo!() } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct KernelToTypeScripts { - pub supported_claim: SupportedClaim, + pub witness: KernelToTypeScriptsWitness, } -impl KernelToTypeScripts { - // TODO: Remove after implementing this struct - pub fn dummy() -> Self { - Self { - supported_claim: SupportedClaim::dummy(), - } +impl KernelToTypeScripts {} + +impl ConsensusProgram for KernelToTypeScripts { + fn source(&self) { + todo!() } -} -impl ValidationLogic for KernelToTypeScripts { - type PrimitiveWitness = PrimitiveWitness; + fn code(&self) -> Vec { + todo!() + } +} - fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { +impl From for KernelToTypeScripts { + fn from(primitive_witness: transaction::PrimitiveWitness) -> Self { let mut type_script_digests = primitive_witness .input_utxos .utxos @@ -65,40 +66,12 @@ impl ValidationLogic for KernelToTypeScripts { .collect_vec(); type_script_digests.sort(); type_script_digests.dedup(); - let claim = Claim { - input: primitive_witness.kernel.mast_hash().values().to_vec(), - output: type_script_digests - .into_iter() - .flat_map(|d| d.values().to_vec()) - .collect_vec(), - // program_hash: Self::program(), - program_digest: Digest::default(), - }; - let supported_claim = SupportedClaim { - claim, - support: ClaimSupport::DummySupport, - }; - Self { supported_claim } - } - - fn prove(&mut self) -> anyhow::Result<()> { - todo!() - } - - fn verify(&self) -> bool { - todo!() - } - - fn validation_program(&self) -> Program { - todo!() - } - - fn support(&self) -> ClaimSupport { - self.supported_claim.support.clone() - } - - fn claim(&self) -> Claim { - self.supported_claim.claim.clone() + Self { + witness: KernelToTypeScriptsWitness { + type_scripts: vec![], + mast_path: vec![], + }, + } } } diff --git a/src/models/blockchain/transaction/validity/lockscripts_halt.rs b/src/models/blockchain/transaction/validity/lockscripts_halt.rs index 1e49c15e3..0ea57b5fc 100644 --- a/src/models/blockchain/transaction/validity/lockscripts_halt.rs +++ b/src/models/blockchain/transaction/validity/lockscripts_halt.rs @@ -1,19 +1,64 @@ use crate::{ - models::consensus::mast_hash::MastHash, + models::{ + blockchain::transaction, + consensus::{ + mast_hash::MastHash, tasm::program::ConsensusProgram, RawWitness, ValidationLogic, + ValidityAstType, ValidityTree, WhichProgram, WitnessType, + }, + }, prelude::{triton_vm, twenty_first}, }; use get_size::GetSize; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use tasm_lib::{triton_vm::program::PublicInput, Digest}; -use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; +use tasm_lib::{ + triton_vm::{ + instruction::LabelledInstruction, + program::{Program, PublicInput}, + proof::Claim, + }, + Digest, +}; +use triton_vm::prelude::{BFieldElement, NonDeterminism}; use twenty_first::shared_math::bfield_codec::BFieldCodec; -use crate::models::{ - blockchain::transaction::{utxo::LockScript, PrimitiveWitness}, - consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, -}; +use crate::models::{blockchain::transaction::utxo::LockScript, consensus::SecretWitness}; + +pub struct LockScriptHalts { + pub program: Program, + pub claim: Claim, + pub raw_witness: RawWitness, +} + +impl From for LockScriptHalts { + fn from(witness: LockScriptHaltsWitness) -> Self { + Self { + claim: Claim::new(witness.lock_script.hash()).with_input( + witness + .transaction_kernel_mast_hash + .reversed() + .values() + .to_vec(), + ), + program: witness.clone().lock_script.program, + raw_witness: witness.nondeterminism().into(), + } + } +} + +impl ValidationLogic for LockScriptHalts { + fn vast(&self) -> ValidityTree { + ValidityTree::new( + ValidityAstType::Atomic( + Some(Box::new(self.program.clone())), + self.claim.clone(), + WhichProgram::LockScriptHalts, + ), + WitnessType::RawWitness(self.raw_witness.clone()), + ) + } +} #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct LockScriptHaltsWitness { @@ -32,10 +77,6 @@ impl SecretWitness for LockScriptHaltsWitness { ) } - fn subprogram(&self) -> Program { - self.lock_script.program.clone() - } - fn standard_input(&self) -> PublicInput { PublicInput::new( self.transaction_kernel_mast_hash @@ -44,80 +85,64 @@ impl SecretWitness for LockScriptHaltsWitness { .to_vec(), ) } + + fn program(&self) -> Program { + self.lock_script.program.clone() + } +} + +impl ConsensusProgram for LockScriptHalts { + fn source(&self) { + todo!() + } + + fn code(&self) -> Vec { + todo!() + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, Default, BFieldCodec)] pub struct LockScriptsHalt { - pub supported_claims: Vec>, + pub witnesses: Vec, } -impl ValidationLogic for LockScriptsHalt { - type PrimitiveWitness = PrimitiveWitness; - - fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> LockScriptsHalt { +impl From for LockScriptsHalt { + fn from(primitive_witness: transaction::PrimitiveWitness) -> Self { let program_and_program_digests_and_spending_keys = primitive_witness .input_lock_scripts .iter() .zip_eq(primitive_witness.lock_script_witnesses.iter()) .map(|(lockscr, spendkey)| (lockscr, lockscr.hash(), spendkey)); let tx_kernel_mast_hash = primitive_witness.kernel.mast_hash(); - let empty_string = vec![]; Self { - supported_claims: program_and_program_digests_and_spending_keys + witnesses: program_and_program_digests_and_spending_keys .into_iter() - .map(|(lockscript, lockscript_digest, spendkey)| { + .map(|(lockscript, _lockscript_digest, spendkey)| { let mut nondeterministic_tokens = spendkey.to_owned(); nondeterministic_tokens.reverse(); - SupportedClaim { - claim: triton_vm::prelude::Claim { - program_digest: lockscript_digest, - input: tx_kernel_mast_hash.values().to_vec(), - output: empty_string.clone(), - }, - support: ClaimSupport::SecretWitness(LockScriptHaltsWitness { - lock_script: lockscript.to_owned(), - nondeterministic_tokens, - transaction_kernel_mast_hash: tx_kernel_mast_hash, - }), + LockScriptHaltsWitness { + lock_script: lockscript.to_owned(), + nondeterministic_tokens, + transaction_kernel_mast_hash: tx_kernel_mast_hash, } }) .collect(), } } +} - fn validation_program(&self) -> Program { - todo!() - } - - fn support(&self) -> ClaimSupport { - ClaimSupport::MultipleSupports( - self.supported_claims - .clone() - .into_iter() - .map(|sc| match sc.support { - ClaimSupport::Proof(_) => todo!(), - ClaimSupport::MultipleSupports(_) => todo!(), - ClaimSupport::SecretWitness(sw) => sw.to_owned(), - ClaimSupport::DummySupport => todo!(), - }) - .collect(), +impl ValidationLogic for LockScriptsHalt { + fn vast(&self) -> ValidityTree { + ValidityTree::new( + ValidityAstType::All( + self.witnesses + .iter() + .cloned() + .map(|witness| LockScriptHalts::from(witness).vast()) + .collect_vec(), + ), + WitnessType::Decomposition, ) } - - fn claim(&self) -> Claim { - let input = self - .supported_claims - .iter() - .flat_map(|sc| sc.claim.program_digest.values().to_vec()) - .collect_vec(); - let output = vec![]; - // let program_hash = AllLockScriptsHalt::program().hash(); - let program_digest = Default::default(); - Claim { - program_digest, - input, - output, - } - } } diff --git a/src/models/blockchain/transaction/validity/removal_records_integrity.rs b/src/models/blockchain/transaction/validity/removal_records_integrity.rs index 6b9186bf6..5e8b6c549 100644 --- a/src/models/blockchain/transaction/validity/removal_records_integrity.rs +++ b/src/models/blockchain/transaction/validity/removal_records_integrity.rs @@ -1,4 +1,6 @@ +use crate::models::blockchain::transaction; use crate::models::consensus::mast_hash::MastHash; +use crate::models::consensus::tasm::program::ConsensusProgram; use crate::prelude::{triton_vm, twenty_first}; use crate::util_types::mutator_set::addition_record::AdditionRecord; use crate::util_types::mutator_set::mutator_set_kernel::get_swbf_indices; @@ -17,16 +19,19 @@ use std::collections::HashMap; use tasm_lib::memory::{encode_to_memory, FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS}; use tasm_lib::structure::tasm_object::TasmObject; use tasm_lib::traits::compiled_program::CompiledProgram; +use tasm_lib::triton_vm::instruction::LabelledInstruction; use tasm_lib::triton_vm::program::PublicInput; use tasm_lib::twenty_first::util_types::mmr::mmr_membership_proof::MmrMembershipProof; use tasm_lib::twenty_first::util_types::mmr::mmr_trait::Mmr; -use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; +use triton_vm::prelude::{BFieldElement, NonDeterminism}; use twenty_first::{ shared_math::{bfield_codec::BFieldCodec, tip5::Digest}, util_types::{algebraic_hasher::AlgebraicHasher, mmr::mmr_accumulator::MmrAccumulator}, }; -use crate::models::consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}; +use crate::models::consensus::{ + SecretWitness, ValidationLogic, ValidityAstType, ValidityTree, WhichProgram, WitnessType, +}; use crate::{ models::blockchain::{ shared::Hash, @@ -88,55 +93,55 @@ impl SecretWitness for RemovalRecordsIntegrityWitness { NonDeterminism::default().with_ram(memory) } - fn subprogram(&self) -> Program { - RemovalRecordsIntegrity::program() - } - fn standard_input(&self) -> PublicInput { PublicInput::new(self.kernel.mast_hash().reversed().values().to_vec()) } + + fn program(&self) -> triton_vm::prelude::Program { + RemovalRecordsIntegrity { + witness: self.clone(), + } + .program() + } } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, FieldCount, BFieldCodec)] pub struct RemovalRecordsIntegrity { - pub supported_claim: SupportedClaim, + pub witness: RemovalRecordsIntegrityWitness, } -impl ValidationLogic for RemovalRecordsIntegrity { - type PrimitiveWitness = PrimitiveWitness; +impl ConsensusProgram for RemovalRecordsIntegrity { + fn source(&self) { + todo!() + } - fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { + fn code(&self) -> Vec { + let program = ::program(); + program.labelled_instructions() + } +} + +impl From for RemovalRecordsIntegrity { + fn from(primitive_witness: transaction::PrimitiveWitness) -> Self { let removal_records_integrity_witness = - RemovalRecordsIntegrityWitness::new(primitive_witness); + RemovalRecordsIntegrityWitness::new(&primitive_witness); Self { - supported_claim: SupportedClaim { - claim: Claim { - program_digest: Hash::hash_varlen(&Self::program().encode()), - input: primitive_witness - .kernel - .mast_hash() - .values() - .into_iter() - .rev() - .collect_vec(), - output: vec![], - }, - support: ClaimSupport::SecretWitness(removal_records_integrity_witness), - }, + witness: removal_records_integrity_witness, } } +} - fn validation_program(&self) -> Program { - Self::program() - } - - fn support(&self) -> ClaimSupport { - self.supported_claim.support.clone() - } - - fn claim(&self) -> Claim { - self.supported_claim.claim.clone() +impl ValidationLogic for RemovalRecordsIntegrity { + fn vast(&self) -> ValidityTree { + ValidityTree { + vast_type: ValidityAstType::Atomic( + Some(Box::new(self.witness.program())), + self.witness.claim(), + WhichProgram::RemovalRecordsIntegrity, + ), + witness_type: WitnessType::RawWitness(self.witness.nondeterminism().into()), + } } } diff --git a/src/models/blockchain/transaction/validity/typescripts_halt.rs b/src/models/blockchain/transaction/validity/typescripts_halt.rs index f8de01e07..3e81c0af3 100644 --- a/src/models/blockchain/transaction/validity/typescripts_halt.rs +++ b/src/models/blockchain/transaction/validity/typescripts_halt.rs @@ -1,141 +1,98 @@ -use crate::{ - models::{ - blockchain::{ - transaction::{primitive_witness::SaltedUtxos, transaction_kernel::TransactionKernel}, - type_scripts::{native_currency::NativeCurrency, TypeScript, TypeScriptWitness}, - }, - consensus::{mast_hash::MastHash, tasm::program::ConsensusProgram}, +use crate::models::{ + blockchain::{ + transaction, + type_scripts::{native_currency::NativeCurrencyWitness, TypeScript, TypeScriptWitness}, }, - prelude::{triton_vm, twenty_first}, + consensus::{ValidationLogic, ValidityAstType, ValidityTree, WitnessType}, }; -use get_size::GetSize; use itertools::Itertools; -use serde::{Deserialize, Serialize}; -use tasm_lib::triton_vm::program::PublicInput; -use triton_vm::prelude::{BFieldElement, Claim, NonDeterminism, Program}; -use twenty_first::shared_math::bfield_codec::BFieldCodec; - -use crate::models::{ - blockchain::transaction::PrimitiveWitness, - consensus::{ClaimSupport, SecretWitness, SupportedClaim, ValidationLogic}, -}; - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub struct BasicTypeScriptWitness { - type_script: TypeScript, - input_utxos: SaltedUtxos, - output_utxos: SaltedUtxos, - transaction_kernel: TransactionKernel, -} -impl SecretWitness for BasicTypeScriptWitness { - fn nondeterminism(&self) -> NonDeterminism { - NonDeterminism::default() - } +// #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] +// pub struct BasicTypeScriptWitness { +// type_script: TypeScript, +// input_utxos: SaltedUtxos, +// output_utxos: SaltedUtxos, +// transaction_kernel: TransactionKernel, +// } + +// impl TypeScriptWitness for BasicTypeScriptWitness { +// fn transaction_kernel(&self) -> TransactionKernel { +// self.transaction_kernel.clone() +// } + +// fn salted_input_utxos(&self) -> SaltedUtxos { +// self.input_utxos.clone() +// } + +// fn salted_output_utxos(&self) -> SaltedUtxos { +// self.output_utxos.clone() +// } + +// fn from_primitive_witness(primitive_transaction_witness: &PrimitiveWitness) -> Self { +// Self { +// type_script: TypeScript::new(NativeCurrency.program()), +// input_utxos: primitive_transaction_witness.input_utxos.clone(), +// output_utxos: primitive_transaction_witness.output_utxos.clone(), +// transaction_kernel: primitive_transaction_witness.kernel.clone(), +// } +// } +// } + +// impl SecretWitness for BasicTypeScriptWitness { +// fn standard_input(&self) -> PublicInput { +// self.type_script_standard_input() +// } + +// fn nondeterminism(&self) -> NonDeterminism { +// todo!() +// } +// } + +// impl ValidationLogic for BasicTypeScriptWitness { +// fn vast(&self) -> ValidityAST { +// ValidityAST::new( +// ValidityAstType::Atomic( +// self.type_script.program, +// Claim::new(self.type_script.hash()) +// .with_input(self.type_script_standard_input().individual_tokens), +// ), +// WitnessType::RawWitness(self.nondeterminism().into()), +// ) +// } +// } - fn subprogram(&self) -> Program { - self.type_script.program.clone() - } - - fn standard_input(&self) -> PublicInput { - self.type_script_standard_input() - } -} - -impl TypeScriptWitness for BasicTypeScriptWitness { - fn transaction_kernel(&self) -> TransactionKernel { - self.transaction_kernel.clone() - } - - fn salted_input_utxos(&self) -> SaltedUtxos { - self.input_utxos.clone() - } - - fn salted_output_utxos(&self) -> SaltedUtxos { - self.output_utxos.clone() - } - - fn from_primitive_witness(primitive_transaction_witness: &PrimitiveWitness) -> Self { - Self { - type_script: TypeScript::new(NativeCurrency::program()), - input_utxos: primitive_transaction_witness.input_utxos.clone(), - output_utxos: primitive_transaction_witness.output_utxos.clone(), - transaction_kernel: primitive_transaction_witness.kernel.clone(), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct TypeScriptsHalt { - pub supported_claims: Vec>, -} - -impl TypeScriptsHalt { - // TODO: Remove after implementing this struct - pub fn dummy() -> Self { - Self { - supported_claims: vec![SupportedClaim::dummy()], - } - } + pub type_scripts: Vec, + pub witnesses: Vec>, } -impl ValidationLogic for TypeScriptsHalt { - type PrimitiveWitness = PrimitiveWitness; - - fn new_from_primitive_witness(primitive_witness: &PrimitiveWitness) -> Self { - let claim = Claim { - input: primitive_witness.kernel.mast_hash().values().to_vec(), - output: vec![], - program_digest: TypeScript::native_currency().hash(), - }; - let witness = BasicTypeScriptWitness { - type_script: TypeScript::native_currency(), - input_utxos: primitive_witness.input_utxos.clone(), - output_utxos: primitive_witness.output_utxos.clone(), - transaction_kernel: primitive_witness.kernel.clone(), - }; - let amount_logic: SupportedClaim = SupportedClaim { - claim, - support: ClaimSupport::SecretWitness(witness), +impl From for TypeScriptsHalt { + fn from(primitive_witness: transaction::PrimitiveWitness) -> Self { + let witness = NativeCurrencyWitness { + input_salted_utxos: primitive_witness.input_utxos.clone(), + output_salted_utxos: primitive_witness.output_utxos.clone(), + kernel: primitive_witness.kernel.clone(), }; + // todo: read out type script hashes + // and look them up Self { - supported_claims: vec![amount_logic], + type_scripts: vec![TypeScript::native_currency()], + witnesses: vec![Box::new(witness)], } } +} - fn validation_program(&self) -> Program { - todo!() - } - - fn support(&self) -> ClaimSupport { - ClaimSupport::MultipleSupports( - self.supported_claims - .clone() - .into_iter() - .map(|sc| match sc.support { - ClaimSupport::Proof(_) => todo!(), - ClaimSupport::MultipleSupports(_) => todo!(), - ClaimSupport::SecretWitness(sw) => sw.to_owned(), - ClaimSupport::DummySupport => todo!(), - }) - .collect(), +impl ValidationLogic for TypeScriptsHalt { + fn vast(&self) -> ValidityTree { + ValidityTree::new( + ValidityAstType::All( + self.witnesses + .iter() + .map(|witness| witness.vast()) + .collect_vec(), + ), + WitnessType::Decomposition, ) } - - fn claim(&self) -> Claim { - let input = self - .supported_claims - .iter() - .flat_map(|sc| sc.claim.program_digest.values().to_vec()) - .collect_vec(); - let output = vec![]; - // let program_hash = AllTypeScriptsHalt::program().hash(); - let program_digest = Default::default(); - Claim { - program_digest, - input, - output, - } - } } diff --git a/src/models/blockchain/type_scripts/mod.rs b/src/models/blockchain/type_scripts/mod.rs index 3b304d870..e0bff9dcd 100644 --- a/src/models/blockchain/type_scripts/mod.rs +++ b/src/models/blockchain/type_scripts/mod.rs @@ -1,7 +1,5 @@ use crate::{ - models::consensus::{ - mast_hash::MastHash, tasm::program::ConsensusProgram, SecretWitness, ValidationLogic, - }, + models::consensus::{mast_hash::MastHash, tasm::program::ConsensusProgram, ValidationLogic}, Hash, }; use get_size::GetSize; @@ -20,23 +18,12 @@ use tasm_lib::{ use self::native_currency::NativeCurrency; -use super::transaction::{ - primitive_witness::{PrimitiveWitness, SaltedUtxos}, - transaction_kernel::TransactionKernel, -}; +use super::transaction::{primitive_witness::SaltedUtxos, transaction_kernel::TransactionKernel}; pub mod native_currency; pub mod neptune_coins; pub mod time_lock; -trait TypeScriptValidationLogic: - ValidationLogic<(PrimitiveWitness, ExternalWitness)> -where - ExternalWitness: BFieldCodec, - (PrimitiveWitness, ExternalWitness): SecretWitness, -{ -} - #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] pub struct TypeScript { pub program: Program, @@ -76,13 +63,12 @@ impl TypeScript { pub fn native_currency() -> Self { Self { - program: NativeCurrency::program(), + program: NativeCurrency.program(), } } } -pub trait TypeScriptWitness { - fn from_primitive_witness(primitive_transaction_witness: &PrimitiveWitness) -> Self; +pub trait TypeScriptWitness: ValidationLogic { fn transaction_kernel(&self) -> TransactionKernel; fn salted_input_utxos(&self) -> SaltedUtxos; fn salted_output_utxos(&self) -> SaltedUtxos; diff --git a/src/models/blockchain/type_scripts/native_currency.rs b/src/models/blockchain/type_scripts/native_currency.rs index df01219fc..3c2b2ff49 100644 --- a/src/models/blockchain/type_scripts/native_currency.rs +++ b/src/models/blockchain/type_scripts/native_currency.rs @@ -1,12 +1,14 @@ use std::collections::HashMap; use crate::models::blockchain::shared::Hash; -use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; +use crate::models::blockchain::transaction; use crate::models::blockchain::transaction::transaction_kernel::{ TransactionKernel, TransactionKernelField, }; use crate::models::consensus::mast_hash::MastHash; -use crate::models::consensus::SecretWitness; +use crate::models::consensus::{ + SecretWitness, ValidationLogic, ValidityAstType, ValidityTree, WhichProgram, WitnessType, +}; use crate::models::{ blockchain::transaction::primitive_witness::SaltedUtxos, consensus::tasm::program::ConsensusProgram, @@ -37,11 +39,11 @@ use super::TypeScriptWitness; /// Transactions that are not balanced in this way are invalid. Furthermore, the /// type script checks that no overflow occurs while computing the sums. #[derive(Debug, Clone, Serialize, Deserialize, BFieldCodec, GetSize, PartialEq, Eq)] -pub struct NativeCurrency {} +pub struct NativeCurrency; impl ConsensusProgram for NativeCurrency { #[allow(clippy::needless_return)] - fn source() { + fn source(&self) { // get in the current program's hash digest let self_digest: Digest = tasm::own_program_digest(); @@ -160,10 +162,8 @@ impl ConsensusProgram for NativeCurrency { assert_eq!(total_input_plus_coinbase, total_output_plus_coinbase); } - fn code() -> Vec { - triton_asm! { - push 1337 - } + fn code(&self) -> Vec { + triton_asm! {} } } @@ -174,6 +174,19 @@ pub struct NativeCurrencyWitness { pub kernel: TransactionKernel, } +impl ValidationLogic for NativeCurrencyWitness { + fn vast(&self) -> ValidityTree { + ValidityTree::new( + ValidityAstType::Atomic( + Some(Box::new(NativeCurrency.program())), + self.claim(), + WhichProgram::NativeCurrency, + ), + WitnessType::RawWitness(self.nondeterminism().into()), + ) + } +} + impl TypeScriptWitness for NativeCurrencyWitness { fn transaction_kernel(&self) -> TransactionKernel { self.kernel.clone() @@ -186,17 +199,23 @@ impl TypeScriptWitness for NativeCurrencyWitness { fn salted_output_utxos(&self) -> SaltedUtxos { self.output_salted_utxos.clone() } +} - fn from_primitive_witness(primitive_transaction_witness: &PrimitiveWitness) -> Self { +impl From for NativeCurrencyWitness { + fn from(primitive_witness: transaction::primitive_witness::PrimitiveWitness) -> Self { Self { - input_salted_utxos: primitive_transaction_witness.input_utxos.clone(), - output_salted_utxos: primitive_transaction_witness.output_utxos.clone(), - kernel: primitive_transaction_witness.kernel.clone(), + input_salted_utxos: primitive_witness.input_utxos.clone(), + output_salted_utxos: primitive_witness.output_utxos.clone(), + kernel: primitive_witness.kernel.clone(), } } } impl SecretWitness for NativeCurrencyWitness { + fn program(&self) -> Program { + NativeCurrency.program() + } + fn standard_input(&self) -> PublicInput { self.type_script_standard_input() } @@ -246,10 +265,6 @@ impl SecretWitness for NativeCurrencyWitness { .with_digests(mast_paths) .with_ram(memory) } - - fn subprogram(&self) -> Program { - NativeCurrency::program() - } } #[cfg(test)] @@ -263,6 +278,8 @@ pub mod test { use proptest_arbitrary_interop::arb; use test_strategy::proptest; + use self::transaction::primitive_witness::PrimitiveWitness; + use super::*; #[proptest] @@ -274,14 +291,14 @@ pub mod test { primitive_witness: PrimitiveWitness, ) { // PrimitiveWitness::arbitrary_with already ensures the transaction is balanced - let native_currency_witness = - NativeCurrencyWitness::from_primitive_witness(&primitive_witness); + let native_currency_witness = NativeCurrencyWitness::from(primitive_witness); assert!( - NativeCurrency::run( - &native_currency_witness.standard_input().individual_tokens, - native_currency_witness.nondeterminism(), - ) - .is_ok(), + NativeCurrency + .run( + &native_currency_witness.standard_input().individual_tokens, + native_currency_witness.nondeterminism(), + ) + .is_ok(), "native currency program did not halt gracefully" ); } @@ -304,14 +321,14 @@ pub mod test { primitive_witness: PrimitiveWitness, ) { // with high probability the amounts (which are random) do not add up - let native_currency_witness = - NativeCurrencyWitness::from_primitive_witness(&primitive_witness); + let native_currency_witness = NativeCurrencyWitness::from(primitive_witness); assert!( - NativeCurrency::run( - &native_currency_witness.standard_input().individual_tokens, - native_currency_witness.nondeterminism(), - ) - .is_err(), + NativeCurrency + .run( + &native_currency_witness.standard_input().individual_tokens, + native_currency_witness.nondeterminism(), + ) + .is_err(), "native currency program failed to panic" ); } @@ -335,14 +352,14 @@ pub mod test { primitive_witness: PrimitiveWitness, ) { // with high probability the amounts (which are random) do not add up - let native_currency_witness = - NativeCurrencyWitness::from_primitive_witness(&primitive_witness); + let native_currency_witness = NativeCurrencyWitness::from(primitive_witness); assert!( - NativeCurrency::run( - &native_currency_witness.standard_input().individual_tokens, - native_currency_witness.nondeterminism(), - ) - .is_err(), + NativeCurrency + .run( + &native_currency_witness.standard_input().individual_tokens, + native_currency_witness.nondeterminism(), + ) + .is_err(), "native currency program failed to panic" ); } diff --git a/src/models/blockchain/type_scripts/neptune_coins.rs b/src/models/blockchain/type_scripts/neptune_coins.rs index e6acf0529..41f9ac154 100644 --- a/src/models/blockchain/type_scripts/neptune_coins.rs +++ b/src/models/blockchain/type_scripts/neptune_coins.rs @@ -80,7 +80,7 @@ impl NeptuneCoins { /// Create a `coins` object for use in a UTXO pub fn to_native_coins(&self) -> Vec { let dictionary = vec![Coin { - type_script_hash: NativeCurrency::hash(), + type_script_hash: NativeCurrency.hash(), state: self.encode(), }]; dictionary diff --git a/src/models/blockchain/type_scripts/time_lock.rs b/src/models/blockchain/type_scripts/time_lock.rs index 2df4694dd..b3a208012 100644 --- a/src/models/blockchain/type_scripts/time_lock.rs +++ b/src/models/blockchain/type_scripts/time_lock.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use crate::models::blockchain::transaction; use crate::models::blockchain::transaction::primitive_witness::arbitrary_primitive_witness_with; use crate::models::blockchain::transaction::primitive_witness::PrimitiveWitness; use crate::models::blockchain::transaction::primitive_witness::SaltedUtxos; @@ -9,6 +10,11 @@ use crate::models::blockchain::transaction::utxo::Coin; use crate::models::blockchain::transaction::PublicAnnouncement; use crate::models::consensus::mast_hash::MastHash; use crate::models::consensus::SecretWitness; +use crate::models::consensus::ValidationLogic; +use crate::models::consensus::ValidityAstType; +use crate::models::consensus::ValidityTree; +use crate::models::consensus::WhichProgram; +use crate::models::consensus::WitnessType; use crate::Hash; use get_size::GetSize; use itertools::Itertools; @@ -21,15 +27,12 @@ use proptest_arbitrary_interop::arb; use serde::{Deserialize, Serialize}; use tasm_lib::memory::encode_to_memory; use tasm_lib::memory::FIRST_NON_DETERMINISTICALLY_INITIALIZED_MEMORY_ADDRESS; +use tasm_lib::triton_vm::program::Program; use tasm_lib::triton_vm::program::PublicInput; use tasm_lib::twenty_first::prelude::AlgebraicHasher; use tasm_lib::twenty_first::shared_math::tip5::Tip5; use tasm_lib::{ - triton_vm::{ - instruction::LabelledInstruction, - program::{NonDeterminism, Program}, - triton_asm, - }, + triton_vm::{instruction::LabelledInstruction, program::NonDeterminism, triton_asm}, twenty_first::shared_math::{b_field_element::BFieldElement, bfield_codec::BFieldCodec}, Digest, }; @@ -41,7 +44,7 @@ use super::neptune_coins::NeptuneCoins; use super::TypeScriptWitness; #[derive(Debug, Clone, Deserialize, Serialize, BFieldCodec, GetSize, PartialEq, Eq)] -pub struct TimeLock {} +pub struct TimeLock; impl TimeLock { /// Create a `TimeLock` type-script-and-state-pair that releases the coins at the @@ -49,7 +52,7 @@ impl TimeLock { /// since the unix epoch started (00:00 am UTC on Jan 1 1970). pub fn until(date: u64) -> Coin { Coin { - type_script_hash: Self::hash(), + type_script_hash: TimeLock.hash(), state: vec![BFieldElement::new(date)], } } @@ -57,7 +60,7 @@ impl TimeLock { impl ConsensusProgram for TimeLock { #[allow(clippy::needless_return)] - fn source() { + fn source(&self) { // get in the current program's hash digest let self_digest: Digest = tasm::own_program_digest(); @@ -123,7 +126,7 @@ impl ConsensusProgram for TimeLock { return; } - fn code() -> Vec { + fn code(&self) -> Vec { // Generated by tasm-lang compiler // `cargo test -- --nocapture typescript_timelock_test` // 2024-02-09 @@ -420,25 +423,52 @@ impl SecretWitness for TimeLockWitness { .with_ram(memory) } - fn subprogram(&self) -> Program { - Program::new(&TimeLock::code()) - } - fn standard_input(&self) -> PublicInput { self.type_script_standard_input() } + + fn program(&self) -> Program { + TimeLock.program() + } +} + +impl ValidationLogic for TimeLockWitness { + fn vast(&self) -> ValidityTree { + ValidityTree::new( + ValidityAstType::Atomic( + Some(Box::new(self.program())), + self.claim(), + WhichProgram::TimeLock, + ), + WitnessType::RawWitness(self.nondeterminism().into()), + ) + } } impl TypeScriptWitness for TimeLockWitness { - fn from_primitive_witness(transaction_primitive_witness: &PrimitiveWitness) -> Self { - let release_dates = transaction_primitive_witness + fn transaction_kernel(&self) -> TransactionKernel { + self.transaction_kernel.clone() + } + + fn salted_input_utxos(&self) -> SaltedUtxos { + self.input_utxos.clone() + } + + fn salted_output_utxos(&self) -> SaltedUtxos { + SaltedUtxos::empty() + } +} + +impl From for TimeLockWitness { + fn from(primitive_witness: transaction::primitive_witness::PrimitiveWitness) -> Self { + let release_dates = primitive_witness .input_utxos .utxos .iter() .map(|utxo| { utxo.coins .iter() - .find(|coin| coin.type_script_hash == TimeLock::hash()) + .find(|coin| coin.type_script_hash == TimeLock {}.hash()) .cloned() .map(|coin| { coin.state @@ -450,26 +480,14 @@ impl TypeScriptWitness for TimeLockWitness { }) .map(|b| b.value()) .collect_vec(); - let transaction_kernel = - TransactionKernel::from_primitive_witness(transaction_primitive_witness); - let input_utxos = transaction_primitive_witness.input_utxos.clone(); + let transaction_kernel = TransactionKernel::from(primitive_witness.clone()); + let input_utxos = primitive_witness.input_utxos.clone(); Self { release_dates, input_utxos, transaction_kernel, } } - fn transaction_kernel(&self) -> TransactionKernel { - self.transaction_kernel.clone() - } - - fn salted_input_utxos(&self) -> SaltedUtxos { - self.input_utxos.clone() - } - - fn salted_output_utxos(&self) -> SaltedUtxos { - SaltedUtxos::empty() - } } impl Arbitrary for TimeLockWitness { @@ -546,7 +564,7 @@ impl Arbitrary for TimeLockWitness { maybe_coinbase, ) .prop_map(move |transaction_primitive_witness| { - TimeLockWitness::from_primitive_witness(&transaction_primitive_witness) + TimeLockWitness::from(transaction_primitive_witness) }) .boxed() }, @@ -569,7 +587,7 @@ mod test { use super::TimeLockWitness; - #[proptest(cases = 5)] + #[proptest] fn test_unlocked( #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, @@ -579,11 +597,12 @@ mod test { time_lock_witness: TimeLockWitness, ) { assert!( - TimeLock::run( - &time_lock_witness.standard_input().individual_tokens, - time_lock_witness.nondeterminism(), - ) - .is_ok(), + TimeLock {} + .run( + &time_lock_witness.standard_input().individual_tokens, + time_lock_witness.nondeterminism(), + ) + .is_ok(), "time lock program did not halt gracefully" ); } @@ -595,7 +614,7 @@ mod test { .as_millis() as u64 } - #[proptest(cases = 5)] + #[proptest] fn test_locked( #[strategy(1usize..=3)] _num_inputs: usize, #[strategy(1usize..=3)] _num_outputs: usize, @@ -607,12 +626,35 @@ mod test { ) { println!("now: {}", now()); assert!( - TimeLock::run( - &time_lock_witness.standard_input().individual_tokens, - time_lock_witness.nondeterminism(), - ) - .is_err(), + TimeLock {} + .run( + &time_lock_witness.standard_input().individual_tokens, + time_lock_witness.nondeterminism(), + ) + .is_err(), "time lock program failed to panic" ); } + + #[proptest] + fn test_released( + #[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))] + _release_dates: Vec, + #[strategy(TimeLockWitness::arbitrary_with((#_release_dates, #_num_outputs, #_num_public_announcements)))] + time_lock_witness: TimeLockWitness, + ) { + println!("now: {}", now()); + assert!( + TimeLock + .run( + &time_lock_witness.standard_input().individual_tokens, + time_lock_witness.nondeterminism(), + ) + .is_ok(), + "time lock program did not halt gracefully" + ); + } } diff --git a/src/models/consensus/mod.rs b/src/models/consensus/mod.rs index 6baa90c33..1e0ed3fff 100644 --- a/src/models/consensus/mod.rs +++ b/src/models/consensus/mod.rs @@ -1,229 +1,341 @@ -use anyhow::Result; +use crate::models::blockchain::type_scripts::native_currency::NativeCurrency; +use crate::models::blockchain::type_scripts::time_lock::TimeLock; +use crate::Hash; use get_size::GetSize; +use itertools::Itertools; use serde::Deserialize; use serde::Serialize; -use tasm_lib::maybe_write_debuggable_program_to_disk; +use strum::Display; +/// This file contains abstractions for verifying consensus logic using TritonVM STARK +/// proofs. The concrete logic is specified in the directories `transaction` and `block`. use tasm_lib::triton_vm; +use tasm_lib::triton_vm::program::Program; use tasm_lib::triton_vm::stark::Stark; -use tasm_lib::triton_vm::vm::VMState; use tasm_lib::twenty_first::shared_math::b_field_element::BFieldElement; use tasm_lib::twenty_first::shared_math::bfield_codec::BFieldCodec; -use tracing::{debug, warn}; +use tasm_lib::Digest; use triton_vm::prelude::Claim; use triton_vm::prelude::NonDeterminism; -use triton_vm::prelude::Program; use triton_vm::prelude::Proof; use triton_vm::prelude::PublicInput; +use self::tasm::program::ConsensusError; +use self::tasm::program::ConsensusProgram; + pub mod mast_hash; pub mod tasm; -/// This file contains abstractions for verifying consensus logic using TritonVM STARK -/// proofs. The concrete logic is specified in the directories `transaction` and `block`. +/// 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 +/// the value true or false and those values propagate up through disjunction and +/// conjunction gates to the root. The validity claim is true iff the root of this +/// tree evaluates to true. +/// +/// Every terminal ("atomic") node in this tree can be supported by a raw witness. +/// Every terminal or non-terminal node in this tree can be supported by a proof. In +/// the end, if the validity claim is valid it should be supported by one (recursive) +/// proof. -/// A Witness is any data that supports the truth of a claim, typically related to the -/// validity of a block or transaction. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub enum Witness { - /// All the first-order witness data that supports the validity claim, including - /// sensitive secrets. - Primitive(PrimitiveWitness), - /// A decomposition of the validity claim into smaller claims, each one of which has - /// its own supporting witness. - ValidationLogic(ValidationLogic), - /// A single proof for the entire claim, typically produced via recursion. - SingleProof(SingleProof), - /// As we do not have recursion yet, sometimes we just need to take things on faith. - /// This must be depracated before mainnet launch! - Faith, +pub enum ValidityAstType { + /// The validity claim is true axiomatically, by definition. E.g.: the genesis + /// block. + Axiom, + /// The root of a validity tree, containing the hash of the object it pertains + /// to. + Root(Digest, Box), + /// A decomposition of the validity claim into a disjunction of smaller claims. + /// The disjunction is true iff one of the subclaims is true. + Any(Vec), + /// A decomposition of the validity claim into a conjunction of smaller claims. + /// The conjunction is true iff all of the subclaims are true. + All(Vec), + /// The validity claim does not decompose into clauses. We have reached the + /// terminal stage of the analytical process. This claim is atomic and there is + /// a dedicated raw witness that proves it. + Atomic(Option>, Claim, WhichProgram), } -/// Single proofs are the final abstaction layer for -/// witnesses. They represent the merger of a set of linked proofs -/// into one. They hide information that linked proofs expose, but -/// the downside is that their production requires multiple runs of the recursive -/// prover. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, BFieldCodec)] -pub struct SingleProof(pub Proof); - -impl GetSize for SingleProof { - fn get_stack_size() -> usize { - std::mem::size_of::() - } - - fn get_heap_size(&self) -> usize { - self.0.get_heap_size() - } - - fn get_size(&self) -> usize { - Self::get_stack_size() + GetSize::get_heap_size(self) +impl core::hash::Hash for ValidityAstType { + fn hash(&self, state: &mut H) { + self.encode().hash(state); } } -pub trait SecretWitness: - Clone + Serialize + PartialEq + Eq + GetSize + BFieldCodec + Sized -{ - /// The program's (public/standard) input - fn standard_input(&self) -> PublicInput; +impl ValidityAstType {} - /// The non-determinism for the VM that this witness corresponds to - fn nondeterminism(&self) -> NonDeterminism; +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec, Hash)] +pub struct RawWitness { + tokens: Vec, + ram: Vec<(BFieldElement, BFieldElement)>, + digests: Vec, +} - /// Returns the subprogram that this secret witness relates to - fn subprogram(&self) -> Program; +impl From> for RawWitness { + fn from(nondeterminism: NonDeterminism) -> Self { + Self { + tokens: nondeterminism.individual_tokens, + ram: nondeterminism.ram.into_iter().collect_vec(), + digests: nondeterminism.digests, + } + } } -/// When a claim to validity decomposes into multiple subclaims via variant -/// `ValidationLogic` of `Witness`, those subclaims are `SupportedClaims`. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub struct SupportedClaim { - pub claim: crate::triton_vm::proof::Claim, - pub support: ClaimSupport, +impl From for NonDeterminism { + fn from(value: RawWitness) -> Self { + Self { + individual_tokens: value.tokens.clone(), + ram: value.ram.iter().cloned().collect(), + digests: value.digests.clone(), + } + } } -/// When a claim to validity decomposes into multiple subclaims via variant -/// `ValidationLogic` of `Witness`, those subclaims pertain to the graceful halting of -/// programs ("subprograms"), which is itself supported by either a proof or some witness -/// that can help the prover produce one. #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec)] -pub enum ClaimSupport { +pub enum WitnessType { + /// A single proof for the entire claim, typically produced via recursion. Proof(Proof), - MultipleSupports(Vec), - SecretWitness(SubprogramWitness), - DummySupport, // TODO: Remove this when all claims are implemented + RawWitness(RawWitness), + /// As we do not have recursion yet, sometimes we just need to take things on faith. + /// This should be depracated before mainnet launch, or just not accepted in the + /// peer loop. + Faith, + /// As the claim decomposes into a conjunction or disjunction or smaller claims, + /// it is those smaller claims not this one that need supporting raw witnesses. + Decomposition, + /// No witness. + None, } -/// SupportedClaim is a helper struct. It -/// encodes a Claim with an optional witness. +impl core::hash::Hash for WitnessType { + fn hash(&self, state: &mut H) { + self.encode().hash(state); + } +} -impl SupportedClaim { - // TODO: REMOVE when all validity logic is implemented - pub fn dummy() -> Self { - let dummy_claim = crate::triton_vm::proof::Claim { - input: Default::default(), - output: Default::default(), - program_digest: Default::default(), - }; +/// An abstract syntax tree that evaluates to true if the block or transaction is +/// valid. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, GetSize, BFieldCodec, Hash)] +pub struct ValidityTree { + pub vast_type: ValidityAstType, + pub witness_type: WitnessType, +} +impl Default for ValidityTree { + fn default() -> Self { Self { - claim: dummy_claim, - support: ClaimSupport::DummySupport, + vast_type: ValidityAstType::Axiom, + witness_type: WitnessType::Faith, } } } -/// A trait for proving and verifying claims to validity of transactions or blocks, -/// sometimes with and sometimes without witness data. -pub trait ValidationLogic { - type PrimitiveWitness; +impl ValidityTree { + pub fn new(vast_type: ValidityAstType, witness_type: WitnessType) -> Self { + Self { + vast_type, + witness_type, + } + } + + pub fn root(object_hash: Digest, tree: ValidityTree) -> Self { + Self { + witness_type: WitnessType::None, + vast_type: ValidityAstType::Root(object_hash, Box::new(tree)), + } + } + + pub fn none() -> Self { + Self { + witness_type: WitnessType::None, + vast_type: ValidityAstType::Axiom, + } + } - fn validation_program(&self) -> Program; - fn support(&self) -> ClaimSupport; - fn claim(&self) -> Claim; + /// Convenience constructor + pub fn all(vasts: Vec) -> Self { + Self { + witness_type: WitnessType::Decomposition, + vast_type: ValidityAstType::All(vasts), + } + } - /// Update witness secret witness to proof - fn upgrade(&mut self, _proof: Proof) { - todo!() + /// Convenience constructor + pub fn any(vasts: Vec) -> Self { + Self { + witness_type: WitnessType::Decomposition, + vast_type: ValidityAstType::Any(vasts), + } } - fn new_from_primitive_witness(primitive_witness: &Self::PrimitiveWitness) -> Self; + /// Convenience constructor + pub fn axiom() -> Self { + Self { + vast_type: ValidityAstType::Axiom, + witness_type: WitnessType::Faith, + } + } - /// Prove the claim. - fn prove(&mut self) -> Result<()> { - match &self.support() { - ClaimSupport::Proof(_) => { - // nothing to do; proof already exists - Ok(()) + pub fn verify(&self, kernel_hash: Digest) -> bool { + match &self.vast_type { + ValidityAstType::Root(object_digest, tree) => { + *object_digest == kernel_hash && tree.verify(kernel_hash) } - ClaimSupport::SecretWitness(witness) => { - // Run program before proving - self.validation_program() - .run( - self.claim().public_input().into(), - witness.nondeterminism().clone(), - ) - .expect("Program execution prior to proving must succeed"); - - let proof = triton_vm::prove( - Stark::default(), - &self.claim(), - &self.validation_program(), - witness.nondeterminism().clone(), - ) - .expect("Proving integrity of removal records must succeed."); - self.upgrade(proof); - Ok(()) + ValidityAstType::Any(clauses) => { + clauses.iter().any(|clause| clause.verify(kernel_hash)) } - ClaimSupport::DummySupport => { - // nothing to do - warn!("Trying to prove claim supported by dummy support"); - Ok(()) + ValidityAstType::All(clauses) => { + clauses.iter().all(|clause| clause.verify(kernel_hash)) } - ClaimSupport::MultipleSupports(_supports) => { - warn!("Trying to prove claim with multiple supports; not supported yet"); - Ok(()) + ValidityAstType::Atomic(maybe_program, claim, which_program) => { + let WitnessType::RawWitness(raw_witness) = &self.witness_type else { + return false; + }; + if let Some(program) = maybe_program { + if program.labelled_instructions().is_empty() { + which_program + .run(claim.input.clone().into(), raw_witness.clone().into()) + .is_ok() + } else { + program + .run(claim.input.clone().into(), raw_witness.clone().into()) + .is_ok() + } + } else { + false + } } + ValidityAstType::Axiom => true, } } - /// Verify the claim. - fn verify(&self) -> bool { - match &self.support() { - ClaimSupport::Proof(proof) => triton_vm::verify(Stark::default(), &self.claim(), proof), - ClaimSupport::SecretWitness(w) => { - let nondeterminism = w.nondeterminism(); - let input = &self.claim().input; - let vm_result = w - .subprogram() - .run(PublicInput::new(input.to_vec()), nondeterminism); - match vm_result { - Ok(observed_output) => { - let found_expected_output = observed_output == self.claim().output; - if !found_expected_output { - warn!("Observed output does not match claimed output for RRI"); - debug!("Got output: {found_expected_output}"); - } - - found_expected_output - } - Err(err) => { - warn!("VM execution for removal records integrity did not halt gracefully"); - debug!("Last state was: {err}"); - false - } - } + pub fn prove(&mut self) { + match &mut self.vast_type { + ValidityAstType::Root(_object_digest, tree) => { + tree.prove(); + self.witness_type = tree.witness_type.clone(); + tree.witness_type = WitnessType::None; + } + ValidityAstType::Axiom => {} + ValidityAstType::Any(branches) => { + branches.iter_mut().for_each(|branch| { + branch.prove(); + }); + // can't use recursion yet, so faith instead + self.witness_type = WitnessType::Faith; + self.vast_type = ValidityAstType::Any(vec![]) } - ClaimSupport::DummySupport => { - warn!("dummy support encountered"); - false + ValidityAstType::All(branches) => { + branches.iter_mut().for_each(|branch| { + branch.prove(); + }); + // can't use recursion yet, so faith instead + self.witness_type = WitnessType::Faith; + self.vast_type = ValidityAstType::All(vec![]) } - ClaimSupport::MultipleSupports(secret_witnesses) => { - let claim = self.claim(); - #[allow(clippy::never_loop)] - for witness in secret_witnesses.iter() { - let public_input = PublicInput::new(claim.input.to_vec()); - let vm_result = witness - .subprogram() - .run(public_input.clone(), witness.nondeterminism()); - match vm_result { - Ok(_) => {} - Err(err) => { - warn!("Multiple-support witness failed to validate: {err}"); - maybe_write_debuggable_program_to_disk( - &witness.subprogram(), - &VMState::new( - &witness.subprogram(), - public_input, - witness.nondeterminism(), - ), - ); - return false; - } + ValidityAstType::Atomic(program, claim, which_program) => { + if program.is_some() + && !program.as_ref().unwrap().labelled_instructions().is_empty() + { + if let WitnessType::RawWitness(raw_witness) = &self.witness_type { + let nondeterminism: NonDeterminism = + raw_witness.clone().into(); + let proof = triton_vm::prove( + Stark::default(), + claim, + program.as_deref().unwrap(), + nondeterminism, + ) + .unwrap_or_else(|_| panic!("proving {which_program} ...")); + *program = None; + self.witness_type = WitnessType::Proof(proof); } } + } + } + } +} + +/// A `SecretWitness` is data that makes a `ConsensusProgram` halt gracefully, but +/// that should be hidden behind a zero-knowledge proof. Phrased differently, after +/// proving the matching `ConsensusProgram`, the `SecretWitness` should be securely +/// deleted. +pub trait SecretWitness { + /// The program's (public/standard) input + fn standard_input(&self) -> PublicInput; + + /// The program's (standard/public) output. + fn output(&self) -> Vec { + vec![] + } + + fn program(&self) -> Program; - true + fn claim(&self) -> Claim { + Claim::new(self.program().hash::()) + .with_input(self.standard_input().individual_tokens) + .with_output(self.output()) + } + + /// The non-determinism for the VM that this witness corresponds to + fn nondeterminism(&self) -> NonDeterminism; + + // fn verify(&self) -> bool { + // if self.consensus_program().code().is_empty() { + // self.consensus_program() + // .program() + // .run(self.standard_input(), self.nondeterminism()) + // .is_ok() + // } else { + // self.consensus_program() + // .run( + // &self.standard_input().individual_tokens, + // self.nondeterminism(), + // ) + // .is_ok() + // } + // } +} + +pub trait ValidationLogic { + fn vast(&self) -> ValidityTree; +} + +/// This enum lists all programs featured anywhere in the consensus logic. It is for +/// development purposes only. After we have recursion, it should be phased out. The +/// purpose of this enum is to a) benefit debugging efforts, and b) rely on rust +/// shadows when we do not have all programs in tasm.`1` +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, BFieldCodec, GetSize, Display)] +pub enum WhichProgram { + LockScriptHalts, + LockScriptsHalt, + TypeScriptsHalt, + TimeLock, + NativeCurrency, + RemovalRecordsIntegrity, + MutatorSetUpdate, + Merger, +} + +impl WhichProgram { + pub fn run( + &self, + public_input: PublicInput, + non_determinism: NonDeterminism, + ) -> Result, ConsensusError> { + match self { + WhichProgram::TimeLock => { + TimeLock.run(&public_input.individual_tokens, non_determinism) + } + WhichProgram::NativeCurrency => { + NativeCurrency.run(&public_input.individual_tokens, non_determinism) } + WhichProgram::LockScriptHalts => todo!(), + WhichProgram::LockScriptsHalt => todo!(), + WhichProgram::TypeScriptsHalt => todo!(), + WhichProgram::RemovalRecordsIntegrity => todo!(), + WhichProgram::MutatorSetUpdate => todo!(), + WhichProgram::Merger => todo!(), } } } diff --git a/src/models/consensus/tasm/program.rs b/src/models/consensus/tasm/program.rs index 46b902d87..ef54d5214 100644 --- a/src/models/consensus/tasm/program.rs +++ b/src/models/consensus/tasm/program.rs @@ -1,4 +1,4 @@ -use std::panic::catch_unwind; +use std::panic::{catch_unwind, RefUnwindSafe}; use itertools::Itertools; use tasm_lib::{ @@ -18,30 +18,36 @@ pub enum ConsensusError { RustShadowPanic(String), } -pub trait ConsensusProgram { +/// A `ConsensusProgram` represents the logic at a terminal (atomic) node of the +/// abstract syntax tree. +pub trait ConsensusProgram +where + Self: RefUnwindSafe, +{ /// The canonical reference source code for the consensus program, written in the /// subset of rust that the tasm-lang compiler understands. To run this program, call /// [`run`][`run`], which spawns a new thread, boots the environment, and executes /// the program. - fn source(); + fn source(&self); /// A derivative of source, in Triton-assembler (tasm) rather than rust. Either /// produced automatically or hand-optimized. - fn code() -> Vec; + fn code(&self) -> Vec; /// Get the program as a `Program` object rather than as a list of `LabelledInstruction`s. - fn program() -> Program { - Program::new(&Self::code()) + fn program(&self) -> Program { + Program::new(&self.code()) } /// Get the program hash digest. - fn hash() -> Digest { - Self::program().hash::() + fn hash(&self) -> Digest { + self.program().hash::() } /// Run the source program natively in rust, but with the emulated TritonVM /// environment for input, output, nondeterminism, and program digest. fn run( + &self, input: &[BFieldElement], nondeterminism: NonDeterminism, ) -> Result, ConsensusError> { @@ -50,8 +56,8 @@ pub trait ConsensusProgram { input.iter().map(|b| b.value()).join(",") ); let emulation_result = catch_unwind(|| { - environment::init(Self::hash(), input, nondeterminism); - Self::source(); + environment::init(self.hash(), input, nondeterminism); + self.source(); environment::PUB_OUTPUT.take() }); match emulation_result { diff --git a/src/models/state/archival_state.rs b/src/models/state/archival_state.rs index 29315d523..35fa69fd9 100644 --- a/src/models/state/archival_state.rs +++ b/src/models/state/archival_state.rs @@ -203,7 +203,8 @@ impl ArchivalState { for addition_record in genesis_block.kernel.body.transaction.kernel.outputs.iter() { archival_mutator_set.ams_mut().add(addition_record); } - archival_mutator_set.set_sync_label(genesis_block.hash()); + let genesis_hash = genesis_block.hash(); + archival_mutator_set.set_sync_label(genesis_hash); archival_mutator_set.persist(); } @@ -787,6 +788,8 @@ impl ArchivalState { #[cfg(test)] mod archival_state_tests { + use std::time::Duration; + use super::*; use crate::config_models::network::Network; @@ -796,6 +799,7 @@ mod archival_state_tests { use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::models::consensus::mast_hash::MastHash; 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; use crate::models::state::wallet::WalletSecret; use crate::models::state::UtxoReceiverData; @@ -803,6 +807,9 @@ mod archival_state_tests { add_block, add_block_to_archival_state, get_mock_global_state, get_mock_wallet_state, make_mock_block_with_valid_pow, make_unit_test_archival_state, unit_test_databases, }; + use rand::rngs::StdRng; + use rand::Rng; + use rand::SeedableRng; use rand::{random, thread_rng, RngCore}; use tracing_test::traced_test; use twenty_first::util_types::storage_vec::traits::*; @@ -821,7 +828,9 @@ mod archival_state_tests { #[tokio::test] async fn initialize_archival_state_test() -> Result<()> { // Ensure that the archival state can be initialized without overflowing the stack + let seed: [u8; 32] = thread_rng().gen(); tokio::spawn(async move { + let mut rng: StdRng = SeedableRng::from_seed(seed); let network = Network::Alpha; let mut archival_state0 = make_test_archival_state(network).await; @@ -831,7 +840,8 @@ mod archival_state_tests { let some_spending_key = some_wallet_secret.nth_generation_spending_key(0); let some_receiving_address = some_spending_key.to_address(); - let (block_1, _, _) = make_mock_block_with_valid_pow(&b, None, some_receiving_address); + let (block_1, _, _) = + make_mock_block_with_valid_pow(&b, None, some_receiving_address, rng.gen()); add_block_to_archival_state(&mut archival_state0, block_1.clone()) .await .unwrap(); @@ -881,10 +891,12 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn archival_state_restore_test() -> Result<()> { + let mut rng = thread_rng(); // Verify that a restored archival mutator set is populated with the right `sync_label` let network = Network::Alpha; let mut archival_state = make_test_archival_state(network).await; - let genesis_wallet_state = get_mock_wallet_state(None, network).await; + let genesis_wallet_state = + get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let (mock_block_1, _, _) = make_mock_block_with_valid_pow( &archival_state.genesis_block, None, @@ -892,6 +904,7 @@ mod archival_state_tests { .wallet_secret .nth_generation_spending_key(0) .to_address(), + rng.gen(), ); archival_state .update_mutator_set(&mock_block_1) @@ -915,14 +928,15 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn update_mutator_set_db_write_test() -> Result<()> { + let mut rng = thread_rng(); // Verify that `update_mutator_set` writes the active window back to disk. let network = Network::Alpha; - let genesis_wallet_state = get_mock_wallet_state(None, network).await; + let genesis_wallet_state = + get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let wallet = genesis_wallet_state.wallet_secret; let own_receiving_address = wallet.nth_generation_spending_key(0).to_address(); - let genesis_receiver_global_state_lock = - get_mock_global_state(network, 0, Some(wallet)).await; + let genesis_receiver_global_state_lock = get_mock_global_state(network, 0, wallet).await; let mut genesis_receiver_global_state = genesis_receiver_global_state_lock.lock_guard_mut().await; @@ -933,6 +947,7 @@ mod archival_state_tests { .genesis_block, None, own_receiving_address, + rng.gen(), ); { @@ -964,11 +979,18 @@ 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); + // 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. { - let (mut mock_block_2, _, _) = - make_mock_block_with_valid_pow(&mock_block_1, None, own_receiving_address); + let (mut mock_block_2, _, _) = make_mock_block_with_valid_pow( + &mock_block_1, + None, + own_receiving_address, + rng.gen(), + ); let sender_tx = genesis_receiver_global_state .create_transaction( vec![UtxoReceiverData { @@ -981,6 +1003,7 @@ mod archival_state_tests { }, }], NeptuneCoins::new(2), + now + seven_months, ) .await .unwrap(); @@ -1011,6 +1034,7 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn update_mutator_set_rollback_ms_block_sync_test() -> Result<()> { + let mut rng = thread_rng(); let network = Network::Alpha; let (mut archival_state, _peer_db_lock, _data_dir) = make_unit_test_archival_state(network).await; @@ -1022,6 +1046,7 @@ mod archival_state_tests { &archival_state.genesis_block, None, own_receiving_address, + rng.gen(), ); archival_state .write_block( @@ -1041,6 +1066,7 @@ mod archival_state_tests { &archival_state.genesis_block, None, own_receiving_address, + rng.gen(), ); archival_state .write_block( @@ -1063,26 +1089,30 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn update_mutator_set_rollback_ms_block_sync_multiple_inputs_outputs_in_block_test() { + let mut rng = thread_rng(); // 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 (mut archival_state, _peer_db_lock, _data_dir) = make_unit_test_archival_state(network).await; - let genesis_wallet_state = get_mock_wallet_state(None, network).await; + let genesis_wallet_state = + 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, Some(genesis_wallet)).await; - let mut num_utxos = Block::premine_distribution().len(); + let global_state_lock = get_mock_global_state(Network::RegTest, 42, genesis_wallet).await; + let mut num_utxos = Block::premine_utxos().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( &archival_state.genesis_block, None, own_receiving_address, + 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 one_money = NeptuneCoins::new(42).to_native_coins(); let receiver_data = vec![ @@ -1108,7 +1138,7 @@ mod archival_state_tests { let sender_tx = global_state_lock .lock_guard_mut() .await - .create_transaction(receiver_data, NeptuneCoins::new(4)) + .create_transaction(receiver_data, NeptuneCoins::new(4), now + seven_months) .await .unwrap(); @@ -1121,7 +1151,7 @@ mod archival_state_tests { .mutator_set_accumulator, ); - assert!(block_1a.is_valid(&genesis_block)); + assert!(block_1a.is_valid(&genesis_block, now + seven_months)); { archival_state @@ -1137,6 +1167,7 @@ mod archival_state_tests { &archival_state.genesis_block, None, own_receiving_address, + rng.gen(), ); archival_state .write_block( @@ -1185,19 +1216,20 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn update_mutator_set_rollback_many_blocks_multiple_inputs_outputs_test() -> Result<()> { + let mut rng = thread_rng(); // Make a rollback of multiple blocks that contains multiple inputs and outputs. // 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 genesis_wallet_state = get_mock_wallet_state(None, Network::Alpha).await; + let genesis_wallet_state = + get_mock_wallet_state(WalletSecret::devnet_wallet(), Network::Alpha).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, Some(genesis_wallet)).await; + 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_distribution().len(); + let mut num_utxos = Block::premine_utxos().len(); let mut previous_block = genesis_block.clone(); // this variable might come in handy for reporting purposes @@ -1207,8 +1239,14 @@ mod archival_state_tests { for i in 0..10 { // Create next block with inputs and outputs - let (mut next_block, _, _) = - make_mock_block_with_valid_pow(&previous_block, None, own_receiving_address); + let (mut next_block, _, _) = make_mock_block_with_valid_pow( + &previous_block, + None, + 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 receiver_data = vec![ UtxoReceiverData { utxo: Utxo { @@ -1230,7 +1268,7 @@ mod archival_state_tests { }, ]; let sender_tx = global_state - .create_transaction(receiver_data, NeptuneCoins::new(4)) + .create_transaction(receiver_data, NeptuneCoins::new(4), now + seven_months) .await .unwrap(); @@ -1240,7 +1278,7 @@ mod archival_state_tests { ); assert!( - next_block.is_valid(&previous_block), + next_block.is_valid(&previous_block, now + seven_months), "next block ({i}) not valid for devnet" ); @@ -1292,8 +1330,12 @@ mod archival_state_tests { { // 3. Create competing block 1 and store it to DB - let (mock_block_1b, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, own_receiving_address); + let (mock_block_1b, _, _) = make_mock_block_with_valid_pow( + &genesis_block, + None, + own_receiving_address, + rng.gen(), + ); global_state .chain .archival_state_mut() @@ -1349,18 +1391,22 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn allow_consumption_of_genesis_output_test() -> Result<()> { + let mut rng = thread_rng(); let network = Network::RegTest; - let genesis_wallet_state = get_mock_wallet_state(None, network).await; + let genesis_wallet_state = + 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 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, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, own_receiving_address); - let global_state_lock = get_mock_global_state(network, 42, Some(genesis_wallet)).await; + 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; // Verify that block_1 that only contains the coinbase output is valid assert!(block_1_a.has_proof_of_work(&genesis_block)); - assert!(block_1_a.is_valid(&genesis_block)); + assert!(block_1_a.is_valid(&genesis_block, now)); // Add a valid input to the block transaction let one_money: NeptuneCoins = NeptuneCoins::new(1); @@ -1376,7 +1422,7 @@ mod archival_state_tests { let sender_tx = global_state_lock .lock_guard_mut() .await - .create_transaction(vec![receiver_data], one_money) + .create_transaction(vec![receiver_data], one_money, now + seven_months) .await .unwrap(); @@ -1386,7 +1432,7 @@ mod archival_state_tests { ); // Block with signed transaction must validate - assert!(block_1_a.is_valid(&genesis_block)); + assert!(block_1_a.is_valid(&genesis_block, now + seven_months)); Ok(()) } @@ -1394,27 +1440,35 @@ mod archival_state_tests { #[traced_test] #[tokio::test] 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 genesis_wallet_state = get_mock_wallet_state(None, network).await; + let genesis_wallet_state = + get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let genesis_spending_key = genesis_wallet_state .wallet_secret .nth_generation_spending_key(0); let genesis_state_lock = - get_mock_global_state(network, 3, Some(genesis_wallet_state.wallet_secret)).await; + get_mock_global_state(network, 3, genesis_wallet_state.wallet_secret).await; let wallet_secret_alice = WalletSecret::new_random(); let alice_spending_key = wallet_secret_alice.nth_generation_spending_key(0); - let alice_state_lock = get_mock_global_state(network, 3, Some(wallet_secret_alice)).await; + let alice_state_lock = get_mock_global_state(network, 3, wallet_secret_alice).await; let wallet_secret_bob = WalletSecret::new_random(); let bob_spending_key = wallet_secret_bob.nth_generation_spending_key(0); - let bob_state_lock = get_mock_global_state(network, 3, Some(wallet_secret_bob)).await; + let bob_state_lock = get_mock_global_state(network, 3, wallet_secret_bob).await; let genesis_block = Block::genesis_block(); + let launch = 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, cb_utxo, cb_output_randomness) = - make_mock_block_with_valid_pow(&genesis_block, None, genesis_spending_key.to_address()); + let (mut block_1, cb_utxo, cb_output_randomness) = make_mock_block_with_valid_pow( + &genesis_block, + None, + genesis_spending_key.to_address(), + rng.gen(), + ); // Send two outputs each to Alice and Bob, from genesis receiver let fee = NeptuneCoins::one(); @@ -1461,26 +1515,25 @@ mod archival_state_tests { }, ]; { - let tx_to_alice_and_bob = genesis_state_lock - .lock_guard_mut() - .await - .create_transaction( - [ - receiver_data_for_alice.clone(), - receiver_data_for_bob.clone(), - ] - .concat(), - fee, - ) - .await - .unwrap(); + let tx_to_alice_and_bob = create_transaction_with_timestamp( + &genesis_state_lock, + &[ + receiver_data_for_alice.clone(), + receiver_data_for_bob.clone(), + ] + .concat(), + fee, + (launch + seven_months).as_millis() as u64, + ) + .await + .unwrap(); // Absorb and verify validity block_1.accumulate_transaction( tx_to_alice_and_bob, &genesis_block.kernel.body.mutator_set_accumulator, ); - assert!(block_1.is_valid(&genesis_block)); + assert!(block_1.is_valid(&genesis_block, launch + seven_months)); } println!("Accumulated transaction into block_1."); @@ -1590,7 +1643,7 @@ mod archival_state_tests { .await .get_wallet_status_for_tip() .await - .synced_unspent_amount + .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) ); assert_eq!( NeptuneCoins::new(200), @@ -1599,7 +1652,7 @@ mod archival_state_tests { .await .get_wallet_status_for_tip() .await - .synced_unspent_amount + .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) ); // Make two transactions: Alice sends two UTXOs to Genesis and Bob sends three UTXOs to genesis @@ -1626,7 +1679,11 @@ mod archival_state_tests { let tx_from_alice = alice_state_lock .lock_guard_mut() .await - .create_transaction(receiver_data_from_alice.clone(), NeptuneCoins::new(1)) + .create_transaction( + receiver_data_from_alice.clone(), + NeptuneCoins::new(1), + launch + seven_months, + ) .await .unwrap(); let receiver_data_from_bob = vec![ @@ -1658,18 +1715,25 @@ mod archival_state_tests { public_announcement: PublicAnnouncement::default(), }, ]; - let tx_from_bob = bob_state_lock - .lock_guard_mut() - .await - .create_transaction(receiver_data_from_bob.clone(), NeptuneCoins::new(2)) - .await - .unwrap(); + let tx_from_bob = create_transaction_with_timestamp( + &bob_state_lock, + &receiver_data_from_bob.clone(), + NeptuneCoins::new(2), + (launch + seven_months).as_millis() as u64, + ) + .await + .unwrap(); // Make block_2 with tx that contains: // - 4 inputs: 2 from Alice and 2 from Bob // - 6 outputs: 2 from Alice to Genesis, 3 from Bob to Genesis, and 1 coinbase to Genesis let (mut block_2, cb_utxo_block_2, cb_sender_randomness_block_2) = - make_mock_block_with_valid_pow(&block_1, None, genesis_spending_key.to_address()); + make_mock_block_with_valid_pow( + &block_1, + None, + genesis_spending_key.to_address(), + rng.gen(), + ); block_2.accumulate_transaction(tx_from_alice, &block_1.kernel.body.mutator_set_accumulator); assert_eq!(2, block_2.kernel.body.transaction.kernel.inputs.len()); assert_eq!(3, block_2.kernel.body.transaction.kernel.outputs.len()); @@ -1679,7 +1743,8 @@ 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()); - assert!(block_2.is_valid(&block_1)); + let now = Duration::from_millis(block_1.kernel.header.timestamp.value()); + assert!(block_2.is_valid(&block_1, now)); // Update chain states for state_lock in [&genesis_state_lock, &alice_state_lock, &bob_state_lock] { @@ -1720,14 +1785,14 @@ mod archival_state_tests { .await .get_wallet_status_for_tip() .await - .synced_unspent_amount + .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) .is_zero()); assert!(bob_state_lock .lock_guard() .await .get_wallet_status_for_tip() .await - .synced_unspent_amount + .synced_unspent_available_amount((launch + seven_months).as_millis() as u64) .is_zero()); // Update genesis wallet and verify that all ingoing UTXOs are recorded @@ -1816,6 +1881,7 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn get_latest_block_test() -> Result<()> { + let mut rng = thread_rng(); let network = Network::Alpha; let mut archival_state: ArchivalState = make_test_archival_state(network).await; @@ -1830,7 +1896,7 @@ mod archival_state_tests { let own_receiving_address = own_wallet.nth_generation_spending_key(0).to_address(); let genesis = *archival_state.genesis_block.clone(); let (mock_block_1, _, _) = - make_mock_block_with_valid_pow(&genesis, None, own_receiving_address); + make_mock_block_with_valid_pow(&genesis, None, own_receiving_address, rng.gen()); add_block_to_archival_state(&mut archival_state, mock_block_1.clone()).await?; let ret1 = archival_state.get_latest_block_from_disk().await?; @@ -1846,7 +1912,7 @@ mod archival_state_tests { // Add a 2nd block and verify that this new block is now returned let (mock_block_2, _, _) = - make_mock_block_with_valid_pow(&mock_block_1, None, own_receiving_address); + make_mock_block_with_valid_pow(&mock_block_1, None, own_receiving_address, rng.gen()); add_block_to_archival_state(&mut archival_state, mock_block_2.clone()).await?; let ret2 = archival_state.get_latest_block_from_disk().await?; assert!( @@ -1866,14 +1932,19 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn get_block_test() -> Result<()> { + let mut rng = thread_rng(); let network = Network::Alpha; let mut archival_state = make_test_archival_state(network).await; let genesis = *archival_state.genesis_block.clone(); let own_wallet = WalletSecret::new_random(); let own_receiving_address = own_wallet.nth_generation_spending_key(0).to_address(); - let (mock_block_1, _, _) = - make_mock_block_with_valid_pow(&genesis.clone(), None, own_receiving_address); + let (mock_block_1, _, _) = make_mock_block_with_valid_pow( + &genesis.clone(), + None, + own_receiving_address, + rng.gen(), + ); // Lookup a block in an empty database, expect None to be returned let ret0 = archival_state.get_block(mock_block_1.hash()).await?; @@ -1895,8 +1966,12 @@ mod archival_state_tests { ); // Inserted a new block and verify that both blocks can be found - let (mock_block_2, _, _) = - make_mock_block_with_valid_pow(&mock_block_1.clone(), None, own_receiving_address); + let (mock_block_2, _, _) = make_mock_block_with_valid_pow( + &mock_block_1.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_2.clone()).await?; let fetched2 = archival_state .get_block(mock_block_2.hash()) @@ -1920,7 +1995,7 @@ mod archival_state_tests { let mut blocks = vec![genesis, mock_block_1, mock_block_2]; for _ in 0..(thread_rng().next_u32() % 20) { let (new_block, _, _) = - make_mock_block_with_valid_pow(&last_block, None, own_receiving_address); + make_mock_block_with_valid_pow(&last_block, None, own_receiving_address, rng.gen()); add_block_to_archival_state(&mut archival_state, new_block.clone()).await?; blocks.push(new_block.clone()); last_block = new_block; @@ -1939,6 +2014,7 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn find_path_simple_test() -> Result<()> { + let mut rng = thread_rng(); let network = Network::Alpha; let mut archival_state = make_test_archival_state(network).await; let genesis = *archival_state.genesis_block.clone(); @@ -1964,12 +2040,20 @@ mod archival_state_tests { // Add a fork with genesis as LUCA and verify that correct results are returned let own_wallet = WalletSecret::new_random(); let own_receiving_address = own_wallet.nth_generation_spending_key(0).to_address(); - let (mock_block_1_a, _, _) = - make_mock_block_with_valid_pow(&genesis.clone(), None, own_receiving_address); + let (mock_block_1_a, _, _) = make_mock_block_with_valid_pow( + &genesis.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_1_a.clone()).await?; - let (mock_block_1_b, _, _) = - make_mock_block_with_valid_pow(&genesis.clone(), None, own_receiving_address); + let (mock_block_1_b, _, _) = make_mock_block_with_valid_pow( + &genesis.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_1_b.clone()).await?; // Test 1a @@ -2024,6 +2108,7 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn fork_path_finding_test() -> Result<()> { + let mut rng = thread_rng(); // Test behavior of fork-resolution functions such as `find_path` and checking if block // belongs to canonical chain. @@ -2107,8 +2192,12 @@ mod archival_state_tests { // Insert a block that is descendant from genesis block and verify that it is canonical let own_wallet = WalletSecret::new_random(); let own_receiving_address = own_wallet.nth_generation_spending_key(0).to_address(); - let (mock_block_1, _, _) = - make_mock_block_with_valid_pow(&genesis.clone(), None, own_receiving_address); + let (mock_block_1, _, _) = make_mock_block_with_valid_pow( + &genesis.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_1.clone()).await?; assert!( archival_state @@ -2130,14 +2219,26 @@ mod archival_state_tests { ); // Insert three more blocks and verify that all are part of the canonical chain - let (mock_block_2_a, _, _) = - make_mock_block_with_valid_pow(&mock_block_1.clone(), None, own_receiving_address); + let (mock_block_2_a, _, _) = make_mock_block_with_valid_pow( + &mock_block_1.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_2_a.clone()).await?; - let (mock_block_3_a, _, _) = - make_mock_block_with_valid_pow(&mock_block_2_a.clone(), None, own_receiving_address); + let (mock_block_3_a, _, _) = make_mock_block_with_valid_pow( + &mock_block_2_a.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_3_a.clone()).await?; - let (mock_block_4_a, _, _) = - make_mock_block_with_valid_pow(&mock_block_3_a.clone(), None, own_receiving_address); + let (mock_block_4_a, _, _) = make_mock_block_with_valid_pow( + &mock_block_3_a.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_4_a.clone()).await?; for (i, block) in [ genesis.clone(), @@ -2175,17 +2276,33 @@ mod archival_state_tests { // Make a tree and verify that the correct parts of the tree are identified as // belonging to the canonical chain - let (mock_block_2_b, _, _) = - make_mock_block_with_valid_pow(&mock_block_1.clone(), None, own_receiving_address); + let (mock_block_2_b, _, _) = make_mock_block_with_valid_pow( + &mock_block_1.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_2_b.clone()).await?; - let (mock_block_3_b, _, _) = - make_mock_block_with_valid_pow(&mock_block_2_b.clone(), None, own_receiving_address); + let (mock_block_3_b, _, _) = make_mock_block_with_valid_pow( + &mock_block_2_b.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_3_b.clone()).await?; - let (mock_block_4_b, _, _) = - make_mock_block_with_valid_pow(&mock_block_3_b.clone(), None, own_receiving_address); + let (mock_block_4_b, _, _) = make_mock_block_with_valid_pow( + &mock_block_3_b.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_4_b.clone()).await?; - let (mock_block_5_b, _, _) = - make_mock_block_with_valid_pow(&mock_block_4_b.clone(), None, own_receiving_address); + let (mock_block_5_b, _, _) = make_mock_block_with_valid_pow( + &mock_block_4_b.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_5_b.clone()).await?; for (i, block) in [ genesis.clone(), @@ -2252,47 +2369,99 @@ mod archival_state_tests { // Note that in the later test, 6b becomes the tip. // Prior to this line, block 4a is tip. - let (mock_block_3_c, _, _) = - make_mock_block_with_valid_pow(&mock_block_2_a.clone(), None, own_receiving_address); + let (mock_block_3_c, _, _) = make_mock_block_with_valid_pow( + &mock_block_2_a.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_3_c.clone()).await?; - let (mock_block_4_c, _, _) = - make_mock_block_with_valid_pow(&mock_block_3_c.clone(), None, own_receiving_address); + let (mock_block_4_c, _, _) = make_mock_block_with_valid_pow( + &mock_block_3_c.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_4_c.clone()).await?; - let (mock_block_5_c, _, _) = - make_mock_block_with_valid_pow(&mock_block_4_c.clone(), None, own_receiving_address); + let (mock_block_5_c, _, _) = make_mock_block_with_valid_pow( + &mock_block_4_c.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_5_c.clone()).await?; - let (mock_block_6_c, _, _) = - make_mock_block_with_valid_pow(&mock_block_5_c.clone(), None, own_receiving_address); + let (mock_block_6_c, _, _) = make_mock_block_with_valid_pow( + &mock_block_5_c.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_6_c.clone()).await?; - let (mock_block_7_c, _, _) = - make_mock_block_with_valid_pow(&mock_block_6_c.clone(), None, own_receiving_address); + let (mock_block_7_c, _, _) = make_mock_block_with_valid_pow( + &mock_block_6_c.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_7_c.clone()).await?; - let (mock_block_8_c, _, _) = - make_mock_block_with_valid_pow(&mock_block_7_c.clone(), None, own_receiving_address); + let (mock_block_8_c, _, _) = make_mock_block_with_valid_pow( + &mock_block_7_c.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_8_c.clone()).await?; - let (mock_block_5_a, _, _) = - make_mock_block_with_valid_pow(&mock_block_4_a.clone(), None, own_receiving_address); + let (mock_block_5_a, _, _) = make_mock_block_with_valid_pow( + &mock_block_4_a.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_5_a.clone()).await?; - let (mock_block_3_d, _, _) = - make_mock_block_with_valid_pow(&mock_block_2_a.clone(), None, own_receiving_address); + let (mock_block_3_d, _, _) = make_mock_block_with_valid_pow( + &mock_block_2_a.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_3_d.clone()).await?; - let (mock_block_4_d, _, _) = - make_mock_block_with_valid_pow(&mock_block_3_d.clone(), None, own_receiving_address); + let (mock_block_4_d, _, _) = make_mock_block_with_valid_pow( + &mock_block_3_d.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_4_d.clone()).await?; - let (mock_block_5_d, _, _) = - make_mock_block_with_valid_pow(&mock_block_4_d.clone(), None, own_receiving_address); + let (mock_block_5_d, _, _) = make_mock_block_with_valid_pow( + &mock_block_4_d.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_5_d.clone()).await?; // This is the most canonical block in the known set - let (mock_block_6_d, _, _) = - make_mock_block_with_valid_pow(&mock_block_5_d.clone(), None, own_receiving_address); + let (mock_block_6_d, _, _) = make_mock_block_with_valid_pow( + &mock_block_5_d.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_6_d.clone()).await?; - let (mock_block_4_e, _, _) = - make_mock_block_with_valid_pow(&mock_block_3_d.clone(), None, own_receiving_address); + let (mock_block_4_e, _, _) = make_mock_block_with_valid_pow( + &mock_block_3_d.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_4_e.clone()).await?; - let (mock_block_5_e, _, _) = - make_mock_block_with_valid_pow(&mock_block_4_e.clone(), None, own_receiving_address); + let (mock_block_5_e, _, _) = make_mock_block_with_valid_pow( + &mock_block_4_e.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_5_e.clone()).await?; for (i, block) in [ @@ -2356,8 +2525,12 @@ mod archival_state_tests { } // Make a new block, 6b, canonical and verify that all checks work - let (mock_block_6_b, _, _) = - make_mock_block_with_valid_pow(&mock_block_5_b.clone(), None, own_receiving_address); + let (mock_block_6_b, _, _) = make_mock_block_with_valid_pow( + &mock_block_5_b.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_6_b.clone()).await?; for (i, block) in [ mock_block_3_c.clone(), @@ -2477,6 +2650,7 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn digest_of_ancestors_test() { + let mut rng = thread_rng(); let mut archival_state = make_test_archival_state(Network::Alpha).await; let genesis = *archival_state.genesis_block.clone(); let own_wallet = WalletSecret::new_random(); @@ -2496,23 +2670,39 @@ mod archival_state_tests { .is_empty()); // Insert blocks and verify that the same result is returned - let (mock_block_1, _, _) = - make_mock_block_with_valid_pow(&genesis.clone(), None, own_receiving_address); + let (mock_block_1, _, _) = make_mock_block_with_valid_pow( + &genesis.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_1.clone()) .await .unwrap(); - let (mock_block_2, _, _) = - make_mock_block_with_valid_pow(&mock_block_1.clone(), None, own_receiving_address); + let (mock_block_2, _, _) = make_mock_block_with_valid_pow( + &mock_block_1.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_2.clone()) .await .unwrap(); - let (mock_block_3, _, _) = - make_mock_block_with_valid_pow(&mock_block_2.clone(), None, own_receiving_address); + let (mock_block_3, _, _) = make_mock_block_with_valid_pow( + &mock_block_2.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_3.clone()) .await .unwrap(); - let (mock_block_4, _, _) = - make_mock_block_with_valid_pow(&mock_block_3.clone(), None, own_receiving_address); + let (mock_block_4, _, _) = make_mock_block_with_valid_pow( + &mock_block_3.clone(), + None, + own_receiving_address, + rng.gen(), + ); add_block_to_archival_state(&mut archival_state, mock_block_4.clone()) .await .unwrap(); @@ -2576,13 +2766,18 @@ mod archival_state_tests { #[traced_test] #[tokio::test] async fn write_block_db_test() -> Result<()> { + let mut rng = thread_rng(); let mut archival_state = make_test_archival_state(Network::Alpha).await; let genesis = *archival_state.genesis_block.clone(); let own_wallet = WalletSecret::new_random(); let own_receiving_address = own_wallet.nth_generation_spending_key(0).to_address(); - let (mock_block_1, _, _) = - make_mock_block_with_valid_pow(&genesis.clone(), None, own_receiving_address); + let (mock_block_1, _, _) = make_mock_block_with_valid_pow( + &genesis.clone(), + None, + own_receiving_address, + rng.gen(), + ); archival_state .write_block( &mock_block_1, @@ -2669,8 +2864,12 @@ mod archival_state_tests { ); // Store another block and verify that this block is appended to disk - let (mock_block_2, _, _) = - make_mock_block_with_valid_pow(&mock_block_1.clone(), None, own_receiving_address); + let (mock_block_2, _, _) = make_mock_block_with_valid_pow( + &mock_block_1.clone(), + None, + own_receiving_address, + rng.gen(), + ); archival_state .write_block( &mock_block_2, diff --git a/src/models/state/mempool.rs b/src/models/state/mempool.rs index ff560a3ce..e716e0f1a 100644 --- a/src/models/state/mempool.rs +++ b/src/models/state/mempool.rs @@ -9,7 +9,8 @@ //! density'. use crate::{ - models::blockchain::type_scripts::neptune_coins::NeptuneCoins, prelude::twenty_first, + models::{blockchain::type_scripts::neptune_coins::NeptuneCoins, consensus::WitnessType}, + prelude::twenty_first, util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator, }; @@ -126,23 +127,17 @@ impl Mempool { None } - /// Insert a transaction into the mempool. It is the caller's responsibility to verify - /// that the transaction is valid and confirmable. + /// Insert a transaction into the mempool. It is the caller's responsibility to validate + /// the transaction. Also, the caller must ensure that the witness type is correct -- + /// this method accepts only fully proven transactions (or, for the time being, faith witnesses). pub fn insert(&mut self, transaction: &Transaction) -> Option { - { - // Early exit on transactions too long into the future. - let horizon = - now() + Duration::from_secs(MEMPOOL_IGNORE_TRANSACTIONS_THIS_MANY_SECS_AHEAD); - - if transaction.kernel.timestamp.value() > horizon.as_millis() as u64 { - return None; - } + match transaction.witness.vast.witness_type { + WitnessType::RawWitness(_) => panic!("Can only insert fully proven transactions into mempool; not accepting raw witnesses."), + WitnessType::Decomposition => panic!("Can only insert fully proven transactions into mempool; not accepting decompositions."), + WitnessType::None => panic!("Can only insert fully proven transactions into mempool; not accepting none."), + WitnessType::Faith => {}, + WitnessType::Proof(_) => {}, } - - // TODO: Do we need to check that the transaction does not exceed a max size? - // I don't think we do since the caller has already checked that the transaction - // is valid. - // If transaction to be inserted conflicts with a transaction that's already // in the mempool we preserve only the one with the highest fee density. if let Some((txid, tx)) = self.transaction_conflicts_with(transaction) { @@ -200,8 +195,8 @@ impl Mempool { self.tx_dictionary.is_empty() } - /// Return a vector with copies of the transactions, in descending order, with - /// the highest fee density not using more than `remaining_storage` bytes. + /// Return a vector with copies of the transactions, in descending order by fee + /// density and using at most `remaining_storage` bytes. pub fn get_transactions_for_block(&self, mut remaining_storage: usize) -> Vec { let mut transactions = vec![]; let mut _fee_acc = NeptuneCoins::zero(); @@ -290,41 +285,47 @@ impl Mempool { self.retain(keep); } - /// This function remove from the mempool all those transactions that become invalid because - /// of this newly mined block. It also updates all mutator set data for the monitored - /// transactions that were not removed due to being included in the block. + /// Remove from the mempool all transactions that become invalid because + /// of this newly mined block. Also update all mutator set data for monitored + /// transactions that were not removed in the previous step. pub fn update_with_block( &mut self, previous_mutator_set_accumulator: MutatorSetAccumulator, block: &Block, ) { - // Check if the sets of inserted indices in the block transaction - // and transactions in the mempool are disjoint. - // Removes the transaction from the mempool if they are not as this would - // mean that at least on of the mempool transaction's inputs are spent in this block. - let sbf_indices_set_by_block: HashSet<_> = block + // The general strategy is to check whether the SWBF index set of a given + // transaction in the mempool is disjoint (*i.e.*, not contained by) the + // SWBF indices coming from the block transaction. If they are not disjoint, + // then remove the transaction from the mempool. + + // Compute the union of all index sets generated by the block transaction. + let swbf_index_set_union: HashSet<_> = block .kernel .body .transaction .kernel .inputs .iter() - .map(|rr| rr.absolute_indices.to_array()) + .flat_map(|rr| rr.absolute_indices.to_array()) .collect(); - // The indices that the input UTXOs would flip are used to determine - // which transactions contain UTXOs that were spent in this block. Any + // The indices that the block transaction inserts are used to determine + // which mempool transactions contain UTXOs that were spent in this block. Any // transaction that contains just *one* input-UTXO that was spent in // this block is invalid let keep = |(_transaction_id, tx): LookupItem| -> bool { - let bloom_filter_indices: HashSet<_> = tx + let transaction_index_sets: HashSet<_> = tx .kernel .inputs .iter() .map(|rr| rr.absolute_indices.to_array()) .collect(); - bloom_filter_indices.is_disjoint(&sbf_indices_set_by_block) + transaction_index_sets.iter().all(|index_set| { + index_set + .iter() + .any(|index| !swbf_index_set_union.contains(index)) + }) }; // Remove the transactions that become invalid with this block @@ -332,7 +333,8 @@ impl Mempool { // Update the remaining transactions so their mutator set data is still valid for tx in self.tx_dictionary.values_mut() { - tx.update_mutator_set_records(&previous_mutator_set_accumulator, block) + *tx = tx + .new_with_updated_mutator_set_records(&previous_mutator_set_accumulator, block) .expect("Updating mempool transaction must succeed"); } @@ -410,9 +412,10 @@ mod tests { util_types::mutator_set::mutator_set_trait::MutatorSet, }; use anyhow::Result; + use itertools::Itertools; use num_bigint::BigInt; use num_traits::Zero; - use rand::random; + use rand::{random, rngs::StdRng, thread_rng, Rng, SeedableRng}; use tracing::debug; use tracing_test::traced_test; use twenty_first::{ @@ -423,7 +426,7 @@ mod tests { pub async fn insert_then_get_then_remove_then_get() { let mut mempool = Mempool::new(ByteSize::gb(1)); let network = Network::Alpha; - let wallet_state = get_mock_wallet_state(None, network).await; + let wallet_state = get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let transaction = make_mock_transaction_with_wallet( vec![], vec![], @@ -452,7 +455,7 @@ mod tests { // Create a mempool with n transactions. async fn setup(transactions_count: u32, network: Network) -> Mempool { let mut mempool = Mempool::new(ByteSize::gb(1)); - let wallet_state = get_mock_wallet_state(None, network).await; + let wallet_state = get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; for i in 0..transactions_count { let t = make_mock_transaction_with_wallet( vec![], @@ -500,7 +503,8 @@ mod tests { #[traced_test] #[tokio::test] async fn prune_stale_transactions() { - let wallet_state = get_mock_wallet_state(None, Network::Alpha).await; + let wallet_state = + get_mock_wallet_state(WalletSecret::devnet_wallet(), Network::Alpha).await; let mut mempool = Mempool::new(ByteSize::gb(1)); assert!( mempool.is_empty(), @@ -539,18 +543,35 @@ mod tests { #[traced_test] #[tokio::test] async fn remove_transactions_with_block_test() -> Result<()> { + let mut rng: StdRng = + SeedableRng::from_rng(thread_rng()).expect("failure lifting thread_rng to StdRng"); + let seed: [u8; 32] = rng.gen(); + // let seed = [ + // 0x19, 0xba, 0xc1, 0x55, 0xa7, 0xa0, 0x33, 0xcc, 0x85, 0x73, 0x47, 0xad, 0xd2, 0x1b, + // 0x4e, 0x30, 0x54, 0x4b, 0xd3, 0x2e, 0xe0, 0xc2, 0x21, 0xe6, 0x96, 0x82, 0x2a, 0x6, 0xe, + // 0xe2, 0xa, 0xda, + // ]; + println!( + "seed: [{}]", + seed.iter().map(|h| format!("{:#x}", h)).join(", ") + ); + + 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 devnet_wallet = WalletSecret::devnet_wallet(); let premine_receiver_global_state_lock = - get_mock_global_state(Network::Alpha, 2, None).await; + get_mock_global_state(Network::Alpha, 2, devnet_wallet).await; let mut premine_receiver_global_state = premine_receiver_global_state_lock.lock_guard_mut().await; + let premine_wallet_secret = &premine_receiver_global_state.wallet_state.wallet_secret; let premine_receiver_spending_key = premine_wallet_secret.nth_generation_spending_key(0); let premine_receiver_address = premine_receiver_spending_key.to_address(); - let other_wallet_secret = WalletSecret::new_random(); + let other_wallet_secret = WalletSecret::new_pseudorandom(rng.gen()); + let other_global_state_lock = - get_mock_global_state(Network::Alpha, 2, Some(other_wallet_secret.clone())).await; + get_mock_global_state(Network::Alpha, 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(); @@ -558,7 +579,7 @@ mod tests { // Ensure that both wallets have a non-zero balance let genesis_block = Block::genesis_block(); let (block_1, coinbase_utxo_1, cb_sender_randomness_1) = - make_mock_block(&genesis_block, None, other_receiver_address); + make_mock_block(&genesis_block, None, other_receiver_address, rng.gen()); // Update both states with block 1 premine_receiver_global_state @@ -610,9 +631,14 @@ 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 tx_by_preminer = premine_receiver_global_state - .create_transaction(output_utxos_generated_by_me, NeptuneCoins::new(1)) + .create_transaction( + output_utxos_generated_by_me, + NeptuneCoins::new(1), + now + seven_months, + ) .await?; // Add this transaction to the mempool @@ -634,13 +660,18 @@ mod tests { public_announcement: PublicAnnouncement::default(), }]; let tx_by_other_original = other_global_state - .create_transaction(output_utxo_data_by_miner, NeptuneCoins::new(1)) + .create_transaction( + output_utxo_data_by_miner, + NeptuneCoins::new(1), + now + seven_months, + ) .await .unwrap(); mempool.insert(&tx_by_other_original); // Create next block which includes preminer's transaction - let (mut block_2, _, _) = make_mock_block(&block_1, None, premine_receiver_address); + let (mut block_2, _, _) = + make_mock_block(&block_1, None, premine_receiver_address, rng.gen()); block_2 .accumulate_transaction(tx_by_preminer, &block_1.kernel.body.mutator_set_accumulator); @@ -649,13 +680,18 @@ mod tests { mempool.update_with_block(block_1.kernel.body.mutator_set_accumulator, &block_2); assert_eq!(1, mempool.len()); - // Create a new block to verify that the non-mined transaction still contains - // valid mutator set data + // Create a new block to verify that the non-mined transaction contains + // updated and valid-again mutator set data let mut tx_by_other_updated: Transaction = mempool.get_transactions_for_block(usize::MAX)[0].clone(); + debug!( + "mempool now has transaction relative to mutator set hash {}", + tx_by_other_updated.kernel.mutator_set_hash.emojihash() + ); + let (block_3_with_no_input, _, _) = - make_mock_block(&block_2, None, premine_receiver_address); + make_mock_block(&block_2, None, premine_receiver_address, rng.gen()); let mut block_3_with_updated_tx = block_3_with_no_input.clone(); debug!( @@ -685,8 +721,9 @@ mod tests { tx_by_other_updated.clone(), &block_2.kernel.body.mutator_set_accumulator, ); + now = Duration::from_millis(block_2.kernel.header.timestamp.value()); assert!( - block_3_with_updated_tx.is_valid(&block_2), + block_3_with_updated_tx.is_valid(&block_2, now + seven_months), "Block with tx with updated mutator set data must be valid" ); @@ -695,7 +732,8 @@ mod tests { // valid. let mut previous_block = block_3_with_no_input; for _ in 0..10 { - let (next_block, _, _) = make_mock_block(&previous_block, None, other_receiver_address); + let (next_block, _, _) = + make_mock_block(&previous_block, None, other_receiver_address, rng.gen()); mempool.update_with_block( previous_block.kernel.body.mutator_set_accumulator, &next_block, @@ -703,15 +741,17 @@ mod tests { previous_block = next_block; } - let (mut block_14, _, _) = make_mock_block(&previous_block, None, other_receiver_address); + let (mut block_14, _, _) = + make_mock_block(&previous_block, None, other_receiver_address, rng.gen()); assert_eq!(Into::::into(14), block_14.kernel.header.height); tx_by_other_updated = mempool.get_transactions_for_block(usize::MAX)[0].clone(); block_14.accumulate_transaction( tx_by_other_updated, &previous_block.kernel.body.mutator_set_accumulator, ); + now = Duration::from_millis(previous_block.kernel.header.timestamp.value()); assert!( - block_14.is_valid(&previous_block), + 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" ); @@ -732,7 +772,10 @@ 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 preminer_state_lock = get_mock_global_state(Network::Alpha, 2, None).await; + 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()); + 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; let premine_spending_key = premine_wallet_secret.nth_generation_spending_key(0); @@ -750,7 +793,11 @@ mod tests { public_announcement: PublicAnnouncement::default(), }; let tx_by_preminer_low_fee = preminer_state - .create_transaction(vec![receiver_data.clone()], NeptuneCoins::new(1)) + .create_transaction( + vec![receiver_data.clone()], + NeptuneCoins::new(1), + now + seven_months, + ) .await?; assert_eq!(0, preminer_state.mempool.len()); @@ -768,7 +815,11 @@ mod tests { // Insert a transaction that spends the same UTXO and has a higher fee. // Verify that this replaces the previous transaction. let tx_by_preminer_high_fee = preminer_state - .create_transaction(vec![receiver_data.clone()], NeptuneCoins::new(10)) + .create_transaction( + vec![receiver_data.clone()], + NeptuneCoins::new(10), + now + seven_months, + ) .await?; preminer_state.mempool.insert(&tx_by_preminer_high_fee); assert_eq!(1, preminer_state.mempool.len()); @@ -783,7 +834,11 @@ mod tests { // Insert a conflicting transaction with a lower fee and verify that it // does *not* replace the existing transaction. let tx_by_preminer_medium_fee = preminer_state - .create_transaction(vec![receiver_data], NeptuneCoins::new(4)) + .create_transaction( + vec![receiver_data], + NeptuneCoins::new(4), + now + seven_months, + ) .await?; preminer_state.mempool.insert(&tx_by_preminer_medium_fee); assert_eq!(1, preminer_state.mempool.len()); diff --git a/src/models/state/mod.rs b/src/models/state/mod.rs index a0f111df2..1d07db7b3 100644 --- a/src/models/state/mod.rs +++ b/src/models/state/mod.rs @@ -4,10 +4,10 @@ use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulat use anyhow::{bail, Result}; use itertools::Itertools; -use num_traits::{CheckedSub, Zero}; +use num_traits::CheckedSub; use std::cmp::max; use std::ops::{Deref, DerefMut}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +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; @@ -15,12 +15,12 @@ use twenty_first::shared_math::digest::Digest; use twenty_first::storage::storage_schema::traits::*; use twenty_first::storage::storage_vec::traits::*; use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; -use twenty_first::util_types::emojihash_trait::Emojihash; use twenty_first::util_types::mmr::mmr_trait::Mmr; use self::blockchain_state::BlockchainState; use self::mempool::Mempool; use self::networking_state::NetworkingState; +use self::wallet::address::generation_address::SpendingKey; use self::wallet::utxo_notification_pool::UtxoNotifier; use self::wallet::wallet_state::WalletState; use self::wallet::wallet_status::WalletStatus; @@ -32,11 +32,12 @@ use super::blockchain::transaction::utxo::{LockScript, Utxo}; use super::blockchain::transaction::validity::TransactionValidationLogic; use super::blockchain::transaction::PublicAnnouncement; use super::blockchain::transaction::Transaction; +use super::blockchain::type_scripts::native_currency::NativeCurrency; 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::ValidationLogic; +use super::consensus::tasm::program::ConsensusProgram; use crate::config_models::cli_args; -use crate::models::consensus::Witness; use crate::models::peer::HandshakeData; use crate::models::state::wallet::monitored_utxo::MonitoredUtxo; use crate::models::state::wallet::utxo_notification_pool::ExpectedUtxo; @@ -360,7 +361,7 @@ impl GlobalState { if let Some((confirming_block, confirmation_timestamp, confirmation_height)) = monitored_utxo.confirmed_in_block { - let amount = monitored_utxo.utxo.get_native_coin_amount(); + let amount = monitored_utxo.utxo.get_native_currency_amount(); history.push(( confirming_block, confirmation_timestamp, @@ -377,143 +378,117 @@ impl GlobalState { history } - /// Create a transaction that sends coins to the given - /// `recipient_utxos` from some selection of owned UTXOs. - /// A change UTXO will be added if needed; the caller - /// does not need to supply this. The caller must supply - /// the fee that they are willing to spend to have this - /// transaction mined. - /// - /// Returns the transaction and a vector containing the sender - /// randomness for each output UTXO. - pub async fn create_transaction( + /// Given the desired outputs, assemble UTXOs that are both spendable + /// (*i.e.*, synced and never or no longer timelocked) and that sum to + /// enough funds. + pub async fn assemble_inputs_for_transaction( &mut self, - receiver_data: Vec, - fee: NeptuneCoins, - ) -> Result { + total_spend: NeptuneCoins, + timestamp: u64, + ) -> Result> { // Get the block tip as the transaction is made relative to it - let bc_tip = self.chain.light_state(); + let block_tip = self.chain.light_state(); - // Get the UTXOs required for this transaction - let total_spend: NeptuneCoins = receiver_data - .iter() - .map(|x| x.utxo.get_native_coin_amount()) - .sum::() - + fee; - - // todo: accomodate a future change whereby this function also returns the matching spending keys + // collect spendable inputs let spendable_utxos_and_mps: Vec<(Utxo, LockScript, MsMembershipProof)> = self .wallet_state - .allocate_sufficient_input_funds_from_lock(total_spend, bc_tip.hash()) + .allocate_sufficient_input_funds_from_lock(total_spend, block_tip.hash(), timestamp) .await?; - // Create all removal records. These must be relative to the block tip. - let msa_tip = &bc_tip.kernel.body.mutator_set_accumulator; + Ok(spendable_utxos_and_mps) + } + + /// Given a list of spendable UTXOs, generate the corresponding removal + /// recods relative to the current mutator set accumulator. + pub fn generate_removal_records( + spendable_utxos_and_mps: &[(Utxo, LockScript, MsMembershipProof)], + mutator_set_accumulator: &MutatorSetAccumulator, + ) -> Vec { let mut inputs: Vec = vec![]; - let mut input_amount: NeptuneCoins = NeptuneCoins::zero(); for (spendable_utxo, _lock_script, mp) in spendable_utxos_and_mps.iter() { - let removal_record = msa_tip.kernel.drop(Hash::hash(spendable_utxo), mp); + let removal_record = mutator_set_accumulator + .kernel + .drop(Hash::hash(spendable_utxo), mp); inputs.push(removal_record); - - input_amount = input_amount + spendable_utxo.get_native_coin_amount(); } + inputs + } - let mut transaction_outputs: Vec = vec![]; - let mut output_utxos: Vec = vec![]; - for rd in receiver_data.iter() { - let addition_record = commit( - Hash::hash(&rd.utxo), - rd.sender_randomness, - rd.receiver_privacy_digest, - ); - transaction_outputs.push(addition_record); - output_utxos.push(rd.utxo.to_owned()); - } + /// Given a list of UTXOs with receiver data, generate the corresponding + /// addition records. + pub fn generate_addition_records(receiver_data: &[UtxoReceiverData]) -> Vec { + receiver_data + .iter() + .map(|rd| { + commit( + Hash::hash(&rd.utxo), + rd.sender_randomness, + rd.receiver_privacy_digest, + ) + }) + .collect_vec() + } - // Send remaining amount back to self - let change_amount = match input_amount.checked_sub(&total_spend) { - Some(amt) => amt, - None => { - bail!("Cannot create change UTXO with negative amount."); - } + /// Generate a change UTXO and transaction output to ensure that the difference + /// in input amount and output amount goes back to us. Also, make sure to expect + /// the UTXO so that we can synchronize it after it is confirmed. + pub async fn add_change(&mut self, change_amount: NeptuneCoins) -> (AdditionRecord, Utxo) { + // generate utxo + let own_spending_key_for_change = self + .wallet_state + .wallet_secret + .nth_generation_spending_key(0); + let own_receiving_address = own_spending_key_for_change.to_address(); + let lock_script = own_receiving_address.lock_script(); + let lock_script_hash = lock_script.hash(); + let change_utxo = Utxo { + coins: change_amount.to_native_coins(), + lock_script_hash, }; - // add change UTXO if necessary - if input_amount > total_spend { - let own_spending_key_for_change = self - .wallet_state - .wallet_secret - .nth_generation_spending_key(0); - let own_receiving_address = own_spending_key_for_change.to_address(); - let lock_script = own_receiving_address.lock_script(); - let lock_script_hash = lock_script.hash(); - let change_utxo = Utxo { - coins: change_amount.to_native_coins(), - lock_script_hash, - }; - let receiver_digest = own_receiving_address.privacy_digest; - let change_sender_randomness = self - .wallet_state - .wallet_secret - .generate_sender_randomness(bc_tip.kernel.header.height, receiver_digest); - let change_addition_record = commit( - Hash::hash(&change_utxo), - change_sender_randomness, - receiver_digest, - ); - transaction_outputs.push(change_addition_record); - output_utxos.push(change_utxo.clone()); - - // Add change UTXO to pool of expected incoming UTXOs - let receiver_preimage = own_spending_key_for_change.privacy_preimage; - let _change_addition_record = self - .wallet_state - .expected_utxos - .add_expected_utxo( - change_utxo, - change_sender_randomness, - receiver_preimage, - UtxoNotifier::Myself, - ) - .expect("Adding change UTXO to UTXO notification pool must succeed"); - } + // generate addition record + let receiver_digest = own_receiving_address.privacy_digest; + let change_sender_randomness = self.wallet_state.wallet_secret.generate_sender_randomness( + self.chain.light_state().kernel.header.height, + receiver_digest, + ); + let change_addition_record = commit( + Hash::hash(&change_utxo), + change_sender_randomness, + receiver_digest, + ); - let public_announcements = receiver_data - .iter() - .map(|x| x.public_announcement.clone()) - .collect_vec(); - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(); + // Add change UTXO to pool of expected incoming UTXOs + let receiver_preimage = own_spending_key_for_change.privacy_preimage; + let _change_addition_record = self + .wallet_state + .expected_utxos + .add_expected_utxo( + change_utxo.clone(), + change_sender_randomness, + receiver_preimage, + UtxoNotifier::Myself, + ) + .expect("Adding change UTXO to UTXO notification pool must succeed"); - let kernel = TransactionKernel { - inputs, - outputs: transaction_outputs, - public_announcements: public_announcements.clone(), - fee, - timestamp: BFieldElement::new(timestamp.try_into().unwrap()), - coinbase: None, - mutator_set_hash: msa_tip.hash(), - }; + (change_addition_record, change_utxo) + } - // TODO: The spending key can be different for each UTXO, and therefore must be supplied by `spendable_utxos_and_mps`. - let spending_key = self - .wallet_state - .wallet_secret - .nth_generation_spending_key(0); + /// Generate a primitive witness for a transaction from various disparate witness data. + pub fn generate_primitive_witness( + spending_key: SpendingKey, + spendable_utxos_and_mps: &[(Utxo, LockScript, MsMembershipProof)], + output_utxos: &[Utxo], + transaction_kernel: &TransactionKernel, + mutator_set_accumulator: MutatorSetAccumulator, + ) -> PrimitiveWitness { + let type_scripts = [NativeCurrency.program(), TimeLock.program()] + .map(TypeScript::new) + .to_vec(); let input_utxos = spendable_utxos_and_mps .iter() - .map(|(utxo, _lock_script, _mp)| utxo) - .cloned() + .map(|(utxo, _lock_script, _mp)| utxo.clone()) .collect_vec(); - - // Right now we only have one type script, namely that for the native Neptune coin. - // Down the line we can and will have other type scripts and these will have to be - // stored and managed in some non-hardcoded database. See issue #92 [1]. - // - // [1]: https://github.com/Neptune-Crypto/neptune-core/issues/92 - let type_scripts = vec![TypeScript::native_currency()]; let input_lock_scripts = spendable_utxos_and_mps .iter() .map(|(_utxo, lock_script, _mp)| lock_script.to_owned()) @@ -524,62 +499,178 @@ impl GlobalState { .cloned() .collect_vec(); - // sanity check: test membership proofs - for (utxo, membership_proof) in input_utxos.iter().zip(input_membership_proofs.iter()) { - let item = Hash::hash(utxo); - assert!(self.chain.light_state().body().mutator_set_accumulator.verify(item, membership_proof), "sanity check failed: trying to generate transaction with invalid membership proofs for inputs!"); - debug!( - "Have valid membership proofs relative to {}", - self.chain - .light_state() - .body() - .mutator_set_accumulator - .hash() - .emojihash() - ); + let secret_input = spending_key.unlock_key.encode(); + + PrimitiveWitness { + input_utxos: SaltedUtxos::new(input_utxos), + input_lock_scripts, + type_scripts, + lock_script_witnesses: vec![secret_input; spendable_utxos_and_mps.len()], + input_membership_proofs, + output_utxos: SaltedUtxos::new(output_utxos.to_vec()), + mutator_set_accumulator, + kernel: transaction_kernel.clone(), } + } + /// Create a transaction that sends coins to the given + /// `recipient_utxos` from some selection of owned UTXOs. + /// A change UTXO will be added if needed; the caller + /// does not need to supply this. The caller must supply + /// the fee that they are willing to spend to have this + /// transaction mined. + /// + /// Returns the transaction and a vector containing the sender + /// randomness for each output UTXO. + pub async fn create_transaction( + &mut self, + receiver_data: Vec, + fee: NeptuneCoins, + timestamp: Duration, + ) -> 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) + .await?; + + // other data + let public_announcements = receiver_data + .iter() + .map(|x| x.public_announcement.clone()) + .collect_vec(); let mutator_set_accumulator = self .chain .light_state() - .body() + .kernel + .body .mutator_set_accumulator .clone(); + let privacy = self.cli().privacy; - let secret_input = spending_key.unlock_key.encode(); - let mut primitive_witness = PrimitiveWitness { - input_utxos: SaltedUtxos::new(input_utxos.clone()), - input_lock_scripts, - type_scripts, - lock_script_witnesses: vec![secret_input; spendable_utxos_and_mps.len()], - input_membership_proofs, - output_utxos: SaltedUtxos::new(output_utxos.clone()), + // TODO: The spending key can be different for each UTXO, and therefore must be supplied by `spendable_utxos_and_mps`. + let spending_key = self + .wallet_state + .wallet_secret + .nth_generation_spending_key(0); + + // assemble transaction object + Ok(Self::create_transaction_from_data( + spending_key, + inputs, + spendable_utxos_and_mps, + outputs, + output_utxos, + fee, + public_announcements, + timestamp.as_millis() as u64, mutator_set_accumulator, - kernel: kernel.clone(), - }; + privacy, + )) + } - // Convert the secret-supported claim to a proof, several proofs, or - // at the very least hide sensitive data. - let mut transaction_validity_logic = - TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); - - if self.cli().privacy { - transaction_validity_logic - .prove() - .expect("Proof generation must work when creating a new transaction"); - } else { - transaction_validity_logic.lock_scripts_halt.prove().expect( - "Proof generation must work when unlocking owned UTXOs for a new transaction.", - ); + /// Given a list of UTXOs with receiver data, assemble owned and synced and spendable + /// UTXOs that unlock enough funds, add (and track) a change UTXO if necessary, and + /// and produce a list of removal records, input UTXOs (with lock scripts and + /// membership proofs), addition records, and output UTXOs. + async fn generate_utxo_data_for_transaction( + &mut self, + receiver_data: &[UtxoReceiverData], + fee: NeptuneCoins, + timestamp: u64, + ) -> Result<( + Vec, + Vec<(Utxo, LockScript, MsMembershipProof)>, + Vec, + Vec, + )> { + // total amount to be spent -- determines how many and which UTXOs to use + let total_spend: NeptuneCoins = receiver_data + .iter() + .map(|x| x.utxo.get_native_currency_amount()) + .sum::() + + fee; + + // collect enough spendable UTXOs + let spendable_utxos_and_mps = self + .assemble_inputs_for_transaction(total_spend, timestamp) + .await?; + let input_amount = spendable_utxos_and_mps + .iter() + .map(|(utxo, _lock_script, _mp)| utxo.get_native_currency_amount()) + .sum::(); + + // sanity check: do we even have enough funds? + if total_spend > input_amount { + bail!("Not enough available funds."); } - // Remove lock script witness from primitive witness to not leak spending keys - primitive_witness.lock_script_witnesses = vec![]; + // create removal records (inputs) + let inputs = Self::generate_removal_records( + &spendable_utxos_and_mps, + &self.chain.light_state().kernel.body.mutator_set_accumulator, + ); + + // create addition records (outputs) + let mut outputs = Self::generate_addition_records(receiver_data); + let mut output_utxos = receiver_data.iter().map(|rd| rd.utxo.clone()).collect_vec(); + + // keep track of change (if any) + if total_spend < input_amount { + let change_amount = input_amount.checked_sub(&total_spend).unwrap(); + let (change_addition_record, change_utxo) = self.add_change(change_amount).await; + outputs.push(change_addition_record); + output_utxos.push(change_utxo.clone()); + } + + Ok((inputs, spendable_utxos_and_mps, outputs, output_utxos)) + } - Ok(Transaction { + /// Assembles a transaction kernel and supporting witness or proof(s) from + /// the given transaction data. + #[allow(clippy::too_many_arguments)] + fn create_transaction_from_data( + spending_key: SpendingKey, + inputs: Vec, + spendable_utxos_and_mps: Vec<(Utxo, LockScript, MsMembershipProof)>, + outputs: Vec, + output_utxos: Vec, + fee: NeptuneCoins, + public_announcements: Vec, + timestamp: u64, + mutator_set_accumulator: MutatorSetAccumulator, + _privacy: bool, + ) -> Transaction { + // complete transaction kernel + let kernel = TransactionKernel { + inputs, + outputs, + public_announcements: public_announcements.clone(), + fee, + timestamp: BFieldElement::new(timestamp), + coinbase: None, + mutator_set_hash: mutator_set_accumulator.hash(), + }; + + // populate witness + let primitive_witness = Self::generate_primitive_witness( + spending_key, + &spendable_utxos_and_mps, + &output_utxos, + &kernel, + mutator_set_accumulator, + ); + + // Convert the validity tree into a single proof. + // Down the line we want to support proving only the lock scripts, or only + // the lock scripts and removal records integrity, but nothing else. + // That's a concern for later though. + let mut transaction_validity_logic = TransactionValidationLogic::from(primitive_witness); + transaction_validity_logic.vast.prove(); + transaction_validity_logic.maybe_primitive_witness = None; + Transaction { kernel, - witness: Witness::ValidationLogic(transaction_validity_logic), - }) + witness: transaction_validity_logic, + } } pub async fn get_own_handshakedata(&self) -> HandshakeData { @@ -1101,9 +1192,13 @@ mod global_state_tests { use crate::{ config_models::network::Network, models::{blockchain::block::Block, state::wallet::utxo_notification_pool::UtxoNotifier}, - tests::shared::{add_block_to_light_state, get_mock_global_state, make_mock_block}, + tests::shared::{ + add_block, add_block_to_light_state, get_mock_global_state, get_mock_wallet_state, + make_mock_block, make_mock_block_with_valid_pow, + }, }; - use num_traits::One; + use num_traits::{One, Zero}; + use rand::{rngs::StdRng, thread_rng, Rng, SeedableRng}; use tracing_test::traced_test; use super::{wallet::WalletSecret, *}; @@ -1134,14 +1229,70 @@ mod global_state_tests { true } + /// Similar to `GlobalState::create_transaction` but with a given timestamp, + /// as opposed to now. + pub(super) async fn create_transaction_with_timestamp( + global_state_lock: &GlobalStateLock, + receiver_data: &[UtxoReceiverData], + fee: NeptuneCoins, + timestamp: u64, + ) -> Result { + // UTXO data: inputs, outputs, and supporting witness data + let (inputs, spendable_utxos_and_mps, outputs, output_utxos) = global_state_lock + .lock_guard_mut() + .await + .generate_utxo_data_for_transaction(receiver_data, fee, timestamp) + .await?; + + // other data + let public_announcements = receiver_data + .iter() + .map(|x| x.public_announcement.clone()) + .collect_vec(); + let mutator_set_accumulator = global_state_lock + .lock_guard_mut() + .await + .chain + .light_state() + .kernel + .body + .mutator_set_accumulator + .clone(); + let privacy = global_state_lock.cli().privacy; + + // TODO: The spending key can be different for each UTXO, and therefore must be supplied by `spendable_utxos_and_mps`. + let spending_key = global_state_lock + .lock_guard_mut() + .await + .wallet_state + .wallet_secret + .nth_generation_spending_key(0); + + // assemble transaction object + Ok(GlobalState::create_transaction_from_data( + spending_key, + inputs, + spendable_utxos_and_mps, + outputs, + output_utxos, + fee, + public_announcements, + timestamp, + mutator_set_accumulator, + privacy, + )) + } + #[traced_test] #[tokio::test] - async fn premine_recipient_can_spend_genesis_block_output() { + async fn premine_recipient_cannot_spend_premine_before_and_can_after_release_date() { let network = Network::Alpha; let other_wallet = WalletSecret::new_random(); - let global_state_lock = get_mock_global_state(network, 2, None).await; - let twenty_amount: NeptuneCoins = NeptuneCoins::new(20); - let twenty_coins = twenty_amount.to_native_coins(); + let global_state_lock = + get_mock_global_state(network, 2, WalletSecret::devnet_wallet()).await; + let genesis_block = Block::genesis_block(); + 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(); let main_lock_script = recipient_address.lock_script(); let output_utxo = Utxo { @@ -1159,14 +1310,47 @@ mod global_state_tests { receiver_privacy_digest, public_announcement, }]; - let tx: Transaction = global_state_lock - .lock_guard_mut() - .await - .create_transaction(receiver_data, NeptuneCoins::new(1)) - .await - .unwrap(); + let monitored_utxos = global_state_lock + .lock_guard() + .await + .wallet_state + .wallet_db + .monitored_utxos() + .get_all(); + 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; + assert!(create_transaction_with_timestamp( + &global_state_lock, + &receiver_data, + NeptuneCoins::new(1), + launch + six_months - one_month, + ) + .await + .is_err()); + + // one month after though, we should be + let mut tx = create_transaction_with_timestamp( + &global_state_lock, + &receiver_data, + NeptuneCoins::new(1), + launch + six_months + one_month, + ) + .await + .unwrap(); assert!(tx.is_valid()); + + // but if we backdate the timestamp two months, not anymore! + tx.kernel.timestamp -= BFieldElement::new(2 * one_month); + // 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); + assert_eq!( 2, tx.kernel.outputs.len(), @@ -1204,12 +1388,14 @@ mod global_state_tests { }); } - let new_tx: Transaction = global_state_lock - .lock_guard_mut() - .await - .create_transaction(other_receiver_data, NeptuneCoins::new(1)) - .await - .unwrap(); + let new_tx: Transaction = create_transaction_with_timestamp( + &global_state_lock, + &other_receiver_data, + NeptuneCoins::new(1), + launch + six_months + one_month, + ) + .await + .unwrap(); assert!(new_tx.is_valid()); assert_eq!( 4, @@ -1226,14 +1412,17 @@ mod global_state_tests { #[traced_test] #[tokio::test] async fn restore_monitored_utxos_from_recovery_data_test() { + let mut rng = thread_rng(); let network = Network::Alpha; - let global_state_lock = get_mock_global_state(network, 2, None).await; + 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 (mock_block_1, _, _) = make_mock_block(&genesis_block, None, other_receiver_address); + let (mock_block_1, _, _) = + make_mock_block(&genesis_block, None, other_receiver_address, rng.gen()); crate::tests::shared::add_block_to_archival_state( global_state.chain.archival_state_mut(), mock_block_1.clone(), @@ -1300,8 +1489,10 @@ mod global_state_tests { #[traced_test] #[tokio::test] async fn resync_ms_membership_proofs_simple_test() -> Result<()> { + let mut rng = thread_rng(); let network = Network::RegTest; - let global_state_lock = get_mock_global_state(network, 2, None).await; + 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 other_receiver_wallet_secret = WalletSecret::new_random(); @@ -1311,7 +1502,10 @@ mod global_state_tests { // 1. Create new block 1 and store it to the DB let genesis_block = Block::genesis_block(); - let (mock_block_1a, _, _) = make_mock_block(&genesis_block, None, other_receiver_address); + let launch = genesis_block.kernel.header.timestamp.value(); + let seven_months = 7 * 30 * 24 * 60 * 60 * 1000; + let (mock_block_1a, _, _) = + make_mock_block(&genesis_block, None, other_receiver_address, rng.gen()); { global_state .chain @@ -1325,7 +1519,9 @@ mod global_state_tests { // Verify that wallet has a monitored UTXO (from genesis) let wallet_status = global_state.get_wallet_status_for_tip().await; - assert!(!wallet_status.synced_unspent_amount.is_zero()); + assert!(!wallet_status + .synced_unspent_available_amount(launch + seven_months) + .is_zero()); // Verify that this is unsynced with mock_block_1a assert!( @@ -1366,8 +1562,10 @@ mod global_state_tests { #[traced_test] #[tokio::test] async fn resync_ms_membership_proofs_fork_test() -> Result<()> { + let mut rng = thread_rng(); let network = Network::RegTest; - let global_state_lock = get_mock_global_state(network, 2, None).await; + 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 own_spending_key = global_state .wallet_state @@ -1378,7 +1576,7 @@ mod global_state_tests { // 1. Create new block 1a where we receive a coinbase UTXO, store it let genesis_block = global_state.chain.archival_state().get_latest_block().await; let (mock_block_1a, coinbase_utxo, coinbase_output_randomness) = - make_mock_block(&genesis_block, None, own_receiving_address); + make_mock_block(&genesis_block, None, own_receiving_address, rng.gen()); { global_state .chain @@ -1421,7 +1619,8 @@ mod global_state_tests { .to_address(); let mut parent_block = genesis_block; for _ in 0..5 { - let (next_block, _, _) = make_mock_block(&parent_block, None, other_receiving_address); + let (next_block, _, _) = + make_mock_block(&parent_block, None, other_receiving_address, rng.gen()); global_state .chain .archival_state_mut() @@ -1482,8 +1681,10 @@ mod global_state_tests { #[traced_test] #[tokio::test] async fn resync_ms_membership_proofs_across_stale_fork() -> Result<()> { + let mut rng = thread_rng(); let network = Network::RegTest; - let global_state_lock = get_mock_global_state(network, 2, None).await; + 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 wallet_secret = global_state.wallet_state.wallet_secret.clone(); let own_spending_key = wallet_secret.nth_generation_spending_key(0); @@ -1497,7 +1698,7 @@ mod global_state_tests { let genesis_block = global_state.chain.archival_state().get_latest_block().await; assert!(genesis_block.kernel.header.height.is_genesis()); let (mock_block_1a, coinbase_utxo_1a, cb_utxo_output_randomness_1a) = - make_mock_block(&genesis_block, None, own_receiving_address); + make_mock_block(&genesis_block, None, own_receiving_address, rng.gen()); { global_state .chain @@ -1537,7 +1738,7 @@ mod global_state_tests { let mut fork_a_block = mock_block_1a.clone(); for _ in 0..100 { let (next_a_block, _, _) = - make_mock_block(&fork_a_block, None, other_receiving_address); + make_mock_block(&fork_a_block, None, other_receiving_address, rng.gen()); global_state .chain .archival_state_mut() @@ -1568,7 +1769,7 @@ mod global_state_tests { let mut fork_b_block = mock_block_1a.clone(); for _ in 0..100 { let (next_b_block, _, _) = - make_mock_block(&fork_b_block, None, other_receiving_address); + make_mock_block(&fork_b_block, None, other_receiving_address, rng.gen()); global_state .chain .archival_state_mut() @@ -1621,7 +1822,7 @@ mod global_state_tests { let mut fork_c_block = genesis_block.clone(); for _ in 0..100 { let (next_c_block, _, _) = - make_mock_block(&fork_c_block, None, other_receiving_address); + make_mock_block(&fork_c_block, None, other_receiving_address, rng.gen()); global_state .chain .archival_state_mut() @@ -1692,4 +1893,340 @@ mod global_state_tests { Ok(()) } + + #[tokio::test] + async fn flaky_mutator_set_test() { + let mut rng: StdRng = + SeedableRng::from_rng(thread_rng()).expect("failure lifting thread_rng to StdRng"); + let seed: [u8; 32] = rng.gen(); + // let seed = [ + // 0xf4, 0xc2, 0x1c, 0xd0, 0x5a, 0xac, 0x99, 0xe7, 0x3a, 0x1e, 0x29, 0x7f, 0x16, 0xc1, + // 0x50, 0x5e, 0x1e, 0xd, 0x4b, 0x49, 0x51, 0x9c, 0x1b, 0xa0, 0x38, 0x3c, 0xd, 0x83, 0x29, + // 0xdb, 0xab, 0xe2, + // ]; + println!( + "seed: [{}]", + seed.iter().map(|h| format!("{:#x}", h)).join(", ") + ); + 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 genesis_wallet_state = + get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; + let genesis_spending_key = genesis_wallet_state + .wallet_secret + .nth_generation_spending_key(0); + let genesis_state_lock = + get_mock_global_state(network, 3, genesis_wallet_state.wallet_secret).await; + + let wallet_secret_alice = WalletSecret::new_pseudorandom(rng.gen()); + let alice_spending_key = wallet_secret_alice.nth_generation_spending_key(0); + let alice_state_lock = get_mock_global_state(network, 3, wallet_secret_alice).await; + + let wallet_secret_bob = WalletSecret::new_pseudorandom(rng.gen()); + 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 launch = genesis_block.kernel.header.timestamp.value(); + let seven_months = 7 * 30 * 24 * 60 * 60 * 1000; + + let (mut block_1, cb_utxo, cb_output_randomness) = make_mock_block_with_valid_pow( + &genesis_block, + None, + genesis_spending_key.to_address(), + rng.gen(), + ); + + // Send two outputs each to Alice and Bob, from genesis receiver + let fee = NeptuneCoins::one(); + let sender_randomness: Digest = rng.gen(); + let receiver_data_for_alice = vec![ + UtxoReceiverData { + public_announcement: PublicAnnouncement::default(), + receiver_privacy_digest: alice_spending_key.to_address().privacy_digest, + sender_randomness, + utxo: Utxo { + lock_script_hash: alice_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(41).to_native_coins(), + }, + }, + UtxoReceiverData { + public_announcement: PublicAnnouncement::default(), + receiver_privacy_digest: alice_spending_key.to_address().privacy_digest, + sender_randomness, + utxo: Utxo { + lock_script_hash: alice_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(59).to_native_coins(), + }, + }, + ]; + // Two outputs for Bob + let receiver_data_for_bob = vec![ + UtxoReceiverData { + public_announcement: PublicAnnouncement::default(), + receiver_privacy_digest: bob_spending_key.to_address().privacy_digest, + sender_randomness, + utxo: Utxo { + lock_script_hash: bob_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(141).to_native_coins(), + }, + }, + UtxoReceiverData { + public_announcement: PublicAnnouncement::default(), + receiver_privacy_digest: bob_spending_key.to_address().privacy_digest, + sender_randomness, + utxo: Utxo { + lock_script_hash: bob_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(59).to_native_coins(), + }, + }, + ]; + { + let tx_to_alice_and_bob = create_transaction_with_timestamp( + &genesis_state_lock, + &[ + receiver_data_for_alice.clone(), + receiver_data_for_bob.clone(), + ] + .concat(), + fee, + launch + seven_months, + ) + .await + .unwrap(); + + // Absorb and verify validity + block_1.accumulate_transaction( + 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))); + } + + println!("Accumulated transaction into block_1."); + println!( + "Transaction has {} inputs (removal records) and {} outputs (addition records)", + block_1.kernel.body.transaction.kernel.inputs.len(), + block_1.kernel.body.transaction.kernel.outputs.len() + ); + + // Update chain states + for state_lock in [&genesis_state_lock, &alice_state_lock, &bob_state_lock] { + let mut state = state_lock.lock_guard_mut().await; + add_block(&mut state, block_1.clone()).await.unwrap(); + state + .chain + .archival_state_mut() + .update_mutator_set(&block_1) + .await + .unwrap(); + } + + { + // Update wallets + let mut genesis_state = genesis_state_lock.lock_guard_mut().await; + genesis_state + .wallet_state + .expected_utxos + .add_expected_utxo( + cb_utxo, + cb_output_randomness, + genesis_spending_key.privacy_preimage, + UtxoNotifier::OwnMiner, + ) + .unwrap(); + genesis_state + .wallet_state + .update_wallet_state_with_new_block( + &genesis_block.kernel.body.mutator_set_accumulator, + &block_1, + ) + .await + .unwrap(); + assert_eq!( + 3, + genesis_state + .wallet_state + .wallet_db + .monitored_utxos() + .len(), "Genesis receiver must have 3 UTXOs after block 1: change from transaction, coinbase from block 1, and the spent premine UTXO" + ); + } + + { + let mut alice_state = alice_state_lock.lock_guard_mut().await; + for rec_data in receiver_data_for_alice { + alice_state + .wallet_state + .expected_utxos + .add_expected_utxo( + rec_data.utxo.clone(), + rec_data.sender_randomness, + alice_spending_key.privacy_preimage, + UtxoNotifier::Cli, + ) + .unwrap(); + } + alice_state + .wallet_state + .update_wallet_state_with_new_block( + &genesis_block.kernel.body.mutator_set_accumulator, + &block_1, + ) + .await + .unwrap(); + } + + { + let mut bob_state = bob_state_lock.lock_guard_mut().await; + for rec_data in receiver_data_for_bob { + bob_state + .wallet_state + .expected_utxos + .add_expected_utxo( + rec_data.utxo.clone(), + rec_data.sender_randomness, + bob_spending_key.privacy_preimage, + UtxoNotifier::Cli, + ) + .unwrap(); + } + bob_state + .wallet_state + .update_wallet_state_with_new_block( + &genesis_block.kernel.body.mutator_set_accumulator, + &block_1, + ) + .await + .unwrap(); + } + + // Now Alice should have a balance of 100 and Bob a balance of 200 + + assert_eq!( + NeptuneCoins::new(100), + alice_state_lock + .lock_guard() + .await + .get_wallet_status_for_tip() + .await + .synced_unspent_available_amount(launch + seven_months) + ); + assert_eq!( + NeptuneCoins::new(200), + bob_state_lock + .lock_guard() + .await + .get_wallet_status_for_tip() + .await + .synced_unspent_available_amount(launch + seven_months) + ); + + // Make two transactions: Alice sends two UTXOs to Genesis and Bob sends three UTXOs to genesis + let receiver_data_from_alice = vec![ + UtxoReceiverData { + utxo: Utxo { + lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(50).to_native_coins(), + }, + sender_randomness: rng.gen(), + receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, + public_announcement: PublicAnnouncement::default(), + }, + UtxoReceiverData { + utxo: Utxo { + lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(49).to_native_coins(), + }, + sender_randomness: rng.gen(), + receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, + public_announcement: PublicAnnouncement::default(), + }, + ]; + let now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + let tx_from_alice = alice_state_lock + .lock_guard_mut() + .await + .create_transaction(receiver_data_from_alice.clone(), NeptuneCoins::new(1), now) + .await + .unwrap(); + let receiver_data_from_bob = vec![ + UtxoReceiverData { + utxo: Utxo { + lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(50).to_native_coins(), + }, + sender_randomness: rng.gen(), + receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, + public_announcement: PublicAnnouncement::default(), + }, + UtxoReceiverData { + utxo: Utxo { + lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(50).to_native_coins(), + }, + sender_randomness: rng.gen(), + receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, + public_announcement: PublicAnnouncement::default(), + }, + UtxoReceiverData { + utxo: Utxo { + lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), + coins: NeptuneCoins::new(98).to_native_coins(), + }, + sender_randomness: rng.gen(), + receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, + public_announcement: PublicAnnouncement::default(), + }, + ]; + let tx_from_bob = bob_state_lock + .lock_guard_mut() + .await + .create_transaction(receiver_data_from_bob.clone(), NeptuneCoins::new(2), now) + .await + .unwrap(); + + // Make block_2 with tx that contains: + // - 4 inputs: 2 from Alice and 2 from Bob + // - 6 outputs: 2 from Alice to Genesis, 3 from Bob to Genesis, and 1 coinbase to Genesis + let (mut block_2, _cb_utxo_block_2, _cb_sender_randomness_block_2) = + make_mock_block_with_valid_pow( + &block_1, + None, + genesis_spending_key.to_address(), + rng.gen(), + ); + block_2.accumulate_transaction(tx_from_alice, &block_1.kernel.body.mutator_set_accumulator); + assert_eq!(2, block_2.kernel.body.transaction.kernel.inputs.len()); + assert_eq!(3, block_2.kernel.body.transaction.kernel.outputs.len()); + + block_2.accumulate_transaction(tx_from_bob, &block_1.kernel.body.mutator_set_accumulator); + } + + #[traced_test] + #[tokio::test] + async fn mock_global_state_is_valid() { + let mut rng = thread_rng(); + let network = Network::RegTest; + 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 now = Duration::from_millis(genesis_block.kernel.header.timestamp.value()); + + let wallet_secret = WalletSecret::new_random(); + let receiving_address = wallet_secret.nth_generation_spending_key(0).to_address(); + let (block_1, _cb_utxo, _cb_output_randomness) = + make_mock_block_with_valid_pow(&genesis_block, None, receiving_address, rng.gen()); + + add_block(&mut global_state, block_1).await.unwrap(); + + assert!(global_state + .chain + .light_state() + .is_valid(&genesis_block, now)); + } } diff --git a/src/models/state/wallet/coin_with_possible_timelock.rs b/src/models/state/wallet/coin_with_possible_timelock.rs new file mode 100644 index 000000000..294ce3b6b --- /dev/null +++ b/src/models/state/wallet/coin_with_possible_timelock.rs @@ -0,0 +1,148 @@ +use std::{fmt::Display, time::Duration}; + +use chrono::DateTime; +use itertools::Itertools; +use num_traits::Zero; +use serde::{Deserialize, Serialize}; + +use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; + +/// 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, +} + +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_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_padding = " ".repeat(release_total_length - string.len()); + (string, string_padding) + } + None => ("".to_string(), " ".repeat(release_total_length)), + }; + + let amount_total_length = 15; + let amount_as_string = self.amount.to_string(); + let amount_parts = amount_as_string.split('.').collect_vec(); + let amount_padding_front = " ".repeat(amount_total_length - 3 - amount_parts[0].len()); + let amount_padding_back = if amount_parts.len() > 1 { + "".to_string() + } else { + " ".to_string() + }; + + write!(f, " {confirmed}{confirmed_padding} {release}{release_padding} {amount_padding_front}{amount_as_string}{amount_padding_back}") + } +} + +impl CoinWithPossibleTimeLock { + pub fn report(coins: &[Self]) -> String { + let confirmed_total_length = 25; + let release_total_length = 25; + let amount_total_length = 15; + let total_length = confirmed_total_length + release_total_length + amount_total_length; + + let confirmed = "confirmed"; + let confirmed_padding = " ".repeat(confirmed_total_length - confirmed.len()); + let release_date = "release_date"; + let release_date_padding = " ".repeat(release_total_length - release_date.len()); + let amount = "amount (NPT)"; + let amount_padding = " ".repeat(amount_total_length - amount.len()); + let heading_with_release = format!("{confirmed}{confirmed_padding} {release_date}{release_date_padding} {amount_padding}{amount}"); + let heading_without_release = format!( + "{confirmed}{confirmed_padding} {} {amount_padding}{amount}", + " ".repeat(release_total_length) + ); + + let mut result = format!("# coins available\n{heading_without_release}\n"); + result = format!("{result}{}\n", "-".repeat(total_length)); + for coin in coins.iter() { + if coin.release_date.is_some() { + continue; + } + result = format!("{result}{coin}\n"); + } + result = format!("{result}\n"); + + let mut result = format!("{result}# time-locked coins\n{heading_with_release}\n"); + result = format!("{result}{}\n", "-".repeat(total_length)); + for coin in coins.iter() { + if coin.release_date.is_none() { + continue; + } + result = format!("{result}{coin}\n"); + } + result = format!("{result}\n"); + + let total_available = coins + .iter() + .filter(|c| c.release_date.is_none()) + .map(|c| c.amount) + .sum::(); + result = format!("{result}total available: {total_available} NPT\n"); + + let total_timelocked = coins + .iter() + .filter(|c| c.release_date.is_some()) + .map(|c| c.amount) + .sum::(); + if !total_timelocked.is_zero() { + result = format!("{result}total time-locked: {total_timelocked} NPT\n"); + } + result + } +} + +#[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 super::CoinWithPossibleTimeLock; + + #[test] + fn sample_report() { + let mut rng = thread_rng(); + let num_coins = rng.gen_range(0..20); + let mut coins = vec![]; + for _ in 0..num_coins { + let coin = CoinWithPossibleTimeLock { + amount: if rng.gen::() { + NeptuneCoins::new(rng.next_u32() % 100000) + } else { + NeptuneCoins::arbitrary(&mut Unstructured::new(&rng.gen::<[u8; 32]>())).unwrap() + }, + release_date: if rng.gen::() { + Some(Duration::from_millis(rng.next_u64() % (1 << 35))) + } else { + None + }, + confirmed: Duration::from_millis(rng.next_u64() % (1 << 35)), + }; + coins.push(coin); + } + + println!("{}", CoinWithPossibleTimeLock::report(&coins)); + } +} diff --git a/src/models/state/wallet/mod.rs b/src/models/state/wallet/mod.rs index 923624c7a..4d2033925 100644 --- a/src/models/state/wallet/mod.rs +++ b/src/models/state/wallet/mod.rs @@ -1,6 +1,7 @@ use crate::prelude::twenty_first; pub mod address; +pub mod coin_with_possible_timelock; pub mod monitored_utxo; pub mod rusty_wallet_database; pub mod utxo_notification_pool; @@ -343,6 +344,8 @@ impl WalletSecret { #[cfg(test)] mod wallet_tests { + use std::time::Duration; + use itertools::Itertools; use num_traits::CheckedSub; use rand::random; @@ -376,10 +379,12 @@ mod wallet_tests { #[tokio::test] async fn wallet_state_constructor_with_genesis_block_test() -> Result<()> { + 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 mut wallet_state_premine_recipient = get_mock_wallet_state(None, network).await; + let mut wallet_state_premine_recipient = + get_mock_wallet_state(WalletSecret::devnet_wallet(), network).await; let monitored_utxos_premine_wallet = get_monitored_utxos(&wallet_state_premine_recipient).await; assert_eq!( @@ -388,21 +393,14 @@ mod wallet_tests { "Monitored UTXO list must contain premined UTXO at init, for premine-wallet" ); - let premine_receiver_spending_key = wallet_state_premine_recipient - .wallet_secret - .nth_generation_spending_key(0); - let premine_receiver_address = premine_receiver_spending_key.to_address(); - let expected_premine_utxo = Utxo { - coins: Block::premine_distribution()[0].1.to_native_coins(), - lock_script_hash: premine_receiver_address.lock_script().hash(), - }; + let expected_premine_utxo = Block::premine_utxos()[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" ); let random_wallet = WalletSecret::new_random(); - let wallet_state_other = get_mock_wallet_state(Some(random_wallet), network).await; + let wallet_state_other = get_mock_wallet_state(random_wallet, network).await; let monitored_utxos_other = get_monitored_utxos(&wallet_state_other).await; assert!( monitored_utxos_other.is_empty(), @@ -419,7 +417,7 @@ mod wallet_tests { for _ in 0..12 { let previous_block = next_block; let (nb, _coinbase_utxo, _sender_randomness) = - make_mock_block(&previous_block, None, other_receiver_address); + make_mock_block(&previous_block, None, other_receiver_address, rng.gen()); next_block = nb; let current_mutator_set_accumulator = previous_block.kernel.body.mutator_set_accumulator.clone(); @@ -453,10 +451,10 @@ 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 own_wallet_secret = WalletSecret::new_random(); - let mut own_wallet_state = - get_mock_wallet_state(Some(own_wallet_secret.clone()), network).await; + let mut own_wallet_state = get_mock_wallet_state(own_wallet_secret.clone(), network).await; let other_wallet_secret = WalletSecret::new_random(); let other_recipient_address = other_wallet_secret .nth_generation_spending_key(0) @@ -472,7 +470,7 @@ mod wallet_tests { 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) = - make_mock_block(&genesis_block, None, own_recipient_address); + make_mock_block(&genesis_block, None, own_recipient_address, rng.gen()); own_wallet_state .expected_utxos @@ -528,8 +526,8 @@ mod wallet_tests { // Create new blocks, verify that the membership proofs are *not* valid // under this block as tip - let (block_2, _, _) = make_mock_block(&block_1, None, other_recipient_address); - let (block_3, _, _) = make_mock_block(&block_2, None, other_recipient_address); + let (block_2, _, _) = make_mock_block(&block_1, None, other_recipient_address, rng.gen()); + let (block_3, _, _) = make_mock_block(&block_2, None, other_recipient_address, rng.gen()); monitored_utxos = get_monitored_utxos(&own_wallet_state).await; { let block_1_tx_output_digest = Hash::hash(&block_1_coinbase_utxo); @@ -541,10 +539,14 @@ mod wallet_tests { .body .mutator_set_accumulator .verify(block_1_tx_output_digest, &ms_membership_proof); - assert!( - !membership_proof_is_valid, - "membership proof must be invalid before updating wallet state" - ); + + // Actually, new blocks / transactions / UTXOs do not necessarily + // invalidate existing mutator set membership proofs (although that is + // what usually happens). So there is no point asserting it. + // assert!( + // !membership_proof_is_valid, + // "membership proof must be invalid before updating wallet state" + // ); } // Verify that the membership proof is valid *after* running the updater own_wallet_state @@ -583,16 +585,21 @@ mod wallet_tests { #[traced_test] #[tokio::test] 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 mut own_wallet_state = get_mock_wallet_state(Some(own_wallet_secret), network).await; + 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 (block_1, cb_utxo, cb_output_randomness) = - make_mock_block(&genesis_block, None, own_spending_key.to_address()); - let mining_reward = cb_utxo.get_native_coin_amount(); + let (block_1, cb_utxo, cb_output_randomness) = make_mock_block( + &genesis_block, + None, + own_spending_key.to_address(), + rng.gen(), + ); + let mining_reward = cb_utxo.get_native_currency_amount(); // Add block to wallet state own_wallet_state @@ -650,8 +657,12 @@ mod wallet_tests { let mut next_block = block_1.clone(); for _ in 0..21 { let previous_block = next_block; - let (next_block_prime, cb_utxo_prime, cb_output_randomness_prime) = - make_mock_block(&previous_block, None, own_spending_key.to_address()); + let (next_block_prime, cb_utxo_prime, cb_output_randomness_prime) = make_mock_block( + &previous_block, + None, + own_spending_key.to_address(), + rng.gen(), + ); own_wallet_state .expected_utxos .add_expected_utxo( @@ -731,8 +742,12 @@ mod wallet_tests { next_block.kernel.header.height ); let msa_tip_previous = next_block.kernel.body.mutator_set_accumulator.clone(); - (next_block, _, _) = - make_mock_block(&next_block.clone(), None, own_spending_key.to_address()); + (next_block, _, _) = make_mock_block( + &next_block.clone(), + None, + own_spending_key.to_address(), + rng.gen(), + ); assert_eq!( Into::::into(23u64), next_block.kernel.header.height @@ -784,33 +799,38 @@ mod wallet_tests { #[traced_test] #[tokio::test] async fn wallet_state_maintanence_multiple_inputs_outputs_test() -> Result<()> { + let mut rng = thread_rng(); // 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 own_wallet_secret = WalletSecret::new_random(); - let mut own_wallet_state = get_mock_wallet_state(Some(own_wallet_secret), network).await; + 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 premine_wallet = get_mock_wallet_state(None, network).await.wallet_secret; + 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, Some(premine_wallet)).await; + get_mock_global_state(Network::Alpha, 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 preminers_original_balance = premine_receiver_global_state .get_wallet_status_for_tip() .await - .synced_unspent_amount; + .synced_unspent_available_amount(launch + seven_months.as_millis() as u64); assert!( !preminers_original_balance.is_zero(), "Premine must have non-zero synced balance" ); let previous_msa = genesis_block.kernel.body.mutator_set_accumulator.clone(); - let (mut block_1, _, _) = make_mock_block(&genesis_block, None, own_address); + let (mut block_1, _, _) = make_mock_block(&genesis_block, None, own_address, rng.gen()); let receiver_data_12_to_other = UtxoReceiverData { public_announcement: PublicAnnouncement::default(), @@ -843,15 +863,20 @@ 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 valid_tx = premine_receiver_global_state - .create_transaction(receiver_data_to_other.clone(), NeptuneCoins::new(2)) + .create_transaction( + receiver_data_to_other.clone(), + NeptuneCoins::new(2), + now + seven_months, + ) .await .unwrap(); block_1.accumulate_transaction(valid_tx, &previous_msa); // Verify the validity of the merged transaction and block - assert!(block_1.is_valid(&genesis_block)); + assert!(block_1.is_valid(&genesis_block, now + seven_months)); // Update wallet state with block_1 let mut monitored_utxos = get_monitored_utxos(&own_wallet_state).await; @@ -882,6 +907,7 @@ mod wallet_tests { .wallet_state .update_wallet_state_with_new_block(&previous_msa, &block_1) .await?; + assert_eq!( preminers_original_balance .checked_sub(&NeptuneCoins::new(15)) @@ -889,7 +915,7 @@ mod wallet_tests { premine_receiver_global_state .get_wallet_status_for_tip() .await - .synced_unspent_amount, + .synced_unspent_available_amount(launch + seven_months.as_millis() as u64), "Preminer must have spent 15: 12 + 1 for sent, 2 for fees" ); @@ -920,7 +946,7 @@ mod wallet_tests { let mut next_block = block_1.clone(); for _ in 0..17 { let previous_block = next_block; - let ret = make_mock_block(&previous_block, None, own_address); + let ret = make_mock_block(&previous_block, None, own_address, rng.gen()); next_block = ret.0; own_wallet_state .expected_utxos @@ -1000,8 +1026,12 @@ mod wallet_tests { .wallet_state .wallet_secret .nth_generation_spending_key(0); - let (block_2_b, _, _) = - make_mock_block(&block_1, None, premine_wallet_spending_key.to_address()); + let (block_2_b, _, _) = make_mock_block( + &block_1, + None, + premine_wallet_spending_key.to_address(), + rng.gen(), + ); own_wallet_state .update_wallet_state_with_new_block( &block_1.kernel.body.mutator_set_accumulator, @@ -1045,8 +1075,12 @@ mod wallet_tests { // Fork back again to the long chain and verify that the membership proofs // all work again - let (block_19, _, _) = - make_mock_block(&block_18, None, premine_wallet_spending_key.to_address()); + let (block_19, _, _) = make_mock_block( + &block_18, + None, + premine_wallet_spending_key.to_address(), + rng.gen(), + ); own_wallet_state .update_wallet_state_with_new_block( &block_18.kernel.body.mutator_set_accumulator, @@ -1080,9 +1114,10 @@ mod wallet_tests { // Fork back to the B-chain with `block_3b` which contains two outputs for `own_wallet`, // 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); + make_mock_block(&block_2_b, None, own_address, rng.gen()); + now = Duration::from_millis(block_3_b.kernel.header.timestamp.value()); assert!( - block_3_b.is_valid(&block_2_b), + block_3_b.is_valid(&block_2_b, now), "Block must be valid before merging txs" ); @@ -1096,7 +1131,7 @@ mod wallet_tests { sender_randomness: random(), }; let tx_from_preminer = premine_receiver_global_state - .create_transaction(vec![receiver_data_six.clone()], NeptuneCoins::new(4)) + .create_transaction(vec![receiver_data_six.clone()], NeptuneCoins::new(4), now) .await .unwrap(); block_3_b.accumulate_transaction( @@ -1104,7 +1139,7 @@ mod wallet_tests { &block_2_b.kernel.body.mutator_set_accumulator, ); assert!( - block_3_b.is_valid(&block_2_b), + block_3_b.is_valid(&block_2_b, now), "Block must be valid after accumulating txs" ); own_wallet_state @@ -1166,8 +1201,12 @@ mod wallet_tests { } // Then fork back to A-chain - let (block_20, _, _) = - make_mock_block(&block_19, None, premine_wallet_spending_key.to_address()); + let (block_20, _, _) = make_mock_block( + &block_19, + None, + premine_wallet_spending_key.to_address(), + rng.gen(), + ); own_wallet_state .update_wallet_state_with_new_block( &block_19.kernel.body.mutator_set_accumulator, diff --git a/src/models/state/wallet/monitored_utxo.rs b/src/models/state/wallet/monitored_utxo.rs index 9fae7b3b0..92b3009ec 100644 --- a/src/models/state/wallet/monitored_utxo.rs +++ b/src/models/state/wallet/monitored_utxo.rs @@ -71,6 +71,8 @@ impl MonitoredUtxo { .map(|x| x.1.clone()) } + /// Get the most recent (block hash, membership proof) entry in the database, + /// if any. pub fn get_latest_membership_proof_entry(&self) -> Option<(Digest, MsMembershipProof)> { self.blockhash_to_membership_proof.iter().next().cloned() } diff --git a/src/models/state/wallet/rusty_wallet_database.rs b/src/models/state/wallet/rusty_wallet_database.rs index 12f92f78d..da4d64f78 100644 --- a/src/models/state/wallet/rusty_wallet_database.rs +++ b/src/models/state/wallet/rusty_wallet_database.rs @@ -52,6 +52,7 @@ impl RustyWalletDatabase { &mut self.monitored_utxos } + /// Get the hash of the block to which this database is synced. pub fn get_sync_label(&self) -> Digest { self.sync_label.get() } diff --git a/src/models/state/wallet/wallet_state.rs b/src/models/state/wallet/wallet_state.rs index 0f420a468..7358410fa 100644 --- a/src/models/state/wallet/wallet_state.rs +++ b/src/models/state/wallet/wallet_state.rs @@ -11,7 +11,7 @@ use std::collections::HashMap; use std::error::Error; use std::fmt::Debug; use std::path::PathBuf; -use std::time::Duration; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::fs::OpenOptions; use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}; use tracing::{debug, error, info, warn}; @@ -23,6 +23,7 @@ use twenty_first::util_types::emojihash_trait::Emojihash; use twenty_first::util_types::storage_schema::traits::*; use twenty_first::util_types::storage_vec::traits::*; +use super::coin_with_possible_timelock::CoinWithPossibleTimeLock; use super::rusty_wallet_database::RustyWalletDatabase; use super::utxo_notification_pool::{UtxoNotificationPool, UtxoNotifier}; use super::wallet_status::{WalletStatus, WalletStatusElement}; @@ -182,7 +183,7 @@ impl WalletState { let sync_label = rusty_wallet_database.get_sync_label(); - let mut ret = Self { + let mut wallet_state = Self { wallet_db: rusty_wallet_database, wallet_secret, number_of_mps_per_utxo: cli_args.number_of_mps_per_utxo, @@ -200,15 +201,12 @@ impl WalletState { // outputs. if sync_label == Digest::default() { // Check if we are premine recipients - let own_spending_key = ret.wallet_secret.nth_generation_spending_key(0); + let own_spending_key = wallet_state.wallet_secret.nth_generation_spending_key(0); let own_receiving_address = own_spending_key.to_address(); - for (premine_receiving_address, amount) in Block::premine_distribution() { - if premine_receiving_address == own_receiving_address { - let coins = amount.to_native_coins(); - let lock_script = own_receiving_address.lock_script(); - let utxo = Utxo::new(lock_script, coins); - - ret.expected_utxos + for utxo in Block::premine_utxos() { + if utxo.lock_script_hash == own_receiving_address.lock_script().hash() { + wallet_state + .expected_utxos .add_expected_utxo( utxo, Digest::default(), @@ -219,15 +217,16 @@ impl WalletState { } } - ret.update_wallet_state_with_new_block( - &MutatorSetAccumulator::default(), - &Block::genesis_block(), - ) - .await - .expect("Updating wallet state with genesis block must succeed"); + wallet_state + .update_wallet_state_with_new_block( + &MutatorSetAccumulator::default(), + &Block::genesis_block(), + ) + .await + .expect("Updating wallet state with genesis block must succeed"); } - ret + wallet_state } /// Return a list of UTXOs spent by this wallet in the transaction @@ -287,7 +286,7 @@ impl WalletState { .collect_vec() } - /// Update wallet state with new block. Assumes the given block + /// Update wallet state with new block. Assume the given block /// is valid and that the wallet state is not up to date yet. pub async fn update_wallet_state_with_new_block( &mut self, @@ -372,6 +371,7 @@ impl WalletState { warn!( "Unable to find valid membership proof for UTXO with digest {utxo_digest}. {confirmed_in_block_info} Current block height is {}", new_block.kernel.header.height ); + // panic!("Unable to find valid membership proof."); } } } @@ -430,7 +430,7 @@ impl WalletState { new_block.kernel.header.height, utxo.coins .iter() - .filter(|coin| coin.type_script_hash == NativeCurrency::hash()) + .filter(|coin| coin.type_script_hash == NativeCurrency.hash()) .map(|coin| *NeptuneCoins::decode(&coin.state) .expect("Failed to decode coin state as amount")) .sum::(), @@ -625,44 +625,32 @@ impl WalletState { let spent = mutxo.spent_in_block.is_some(); if let Some(mp) = mutxo.get_membership_proof_for_block(tip_digest) { if spent { - synced_spent.push(WalletStatusElement(mp.auth_path_aocl.leaf_index, utxo)); + synced_spent.push(WalletStatusElement::new(mp.auth_path_aocl.leaf_index, utxo)); } else { synced_unspent.push(( - WalletStatusElement(mp.auth_path_aocl.leaf_index, utxo), + WalletStatusElement::new(mp.auth_path_aocl.leaf_index, utxo), mp.clone(), )); } } else { let any_mp = &mutxo.blockhash_to_membership_proof.iter().next().unwrap().1; if spent { - unsynced_spent - .push(WalletStatusElement(any_mp.auth_path_aocl.leaf_index, utxo)); + unsynced_spent.push(WalletStatusElement::new( + any_mp.auth_path_aocl.leaf_index, + utxo, + )); } else { - unsynced_unspent - .push(WalletStatusElement(any_mp.auth_path_aocl.leaf_index, utxo)); + unsynced_unspent.push(WalletStatusElement::new( + any_mp.auth_path_aocl.leaf_index, + utxo, + )); } } } WalletStatus { - synced_unspent_amount: synced_unspent - .iter() - .map(|x| x.0 .1.get_native_coin_amount()) - .sum(), synced_unspent, - unsynced_unspent_amount: unsynced_unspent - .iter() - .map(|x| x.1.get_native_coin_amount()) - .sum(), unsynced_unspent, - synced_spent_amount: synced_spent - .iter() - .map(|x| x.1.get_native_coin_amount()) - .sum(), synced_spent, - unsynced_spent_amount: unsynced_spent - .iter() - .map(|x| x.1.get_native_coin_amount()) - .sum(), unsynced_spent, } } @@ -671,6 +659,7 @@ impl WalletState { &self, requested_amount: NeptuneCoins, tip_digest: Digest, + timestamp: u64, ) -> 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 @@ -678,12 +667,16 @@ impl WalletState { let wallet_status = self.get_wallet_status_from_lock(tip_digest); // First check that we have enough. Otherwise return an error. - if wallet_status.synced_unspent_amount < requested_amount { - // TODO: Change this to `Display` print once available. + if wallet_status.synced_unspent_available_amount(timestamp) < requested_amount { bail!( - "Insufficient synced amount to create transaction. Requested: {:?}, synced unspent amount: {:?}. Unsynced unspent amount: {:?}. Block is: {}", + "Insufficient synced amount to create transaction. Requested: {}, Total synced UTXOs: {}. Total synced amount: {}. Synced unspent available amount: {}. Synced unspent timelocked amount: {}. Total unsynced UTXOs: {}. Unsynced unspent amount: {}. Block is: {}", requested_amount, - wallet_status.synced_unspent_amount, wallet_status.unsynced_unspent_amount, + wallet_status.synced_unspent.len(), + wallet_status.synced_unspent.iter().map(|(wse, _msmp)| wse.utxo.get_native_currency_amount()).sum::(), + wallet_status.synced_unspent_available_amount(timestamp), + wallet_status.synced_unspent_timelocked_amount(timestamp), + wallet_status.unsynced_unspent.len(), + wallet_status.unsynced_unspent_amount(), tip_digest.emojihash()); } @@ -697,9 +690,10 @@ impl WalletState { while allocated_amount < requested_amount { let (wallet_status_element, membership_proof) = wallet_status.synced_unspent[ret.len()].clone(); - allocated_amount = allocated_amount + wallet_status_element.1.get_native_coin_amount(); + allocated_amount = + allocated_amount + wallet_status_element.utxo.get_native_currency_amount(); ret.push(( - wallet_status_element.1, + wallet_status_element.utxo, lock_script.clone(), membership_proof, )); @@ -715,19 +709,46 @@ impl WalletState { requested_amount: NeptuneCoins, tip_digest: Digest, ) -> Result> { - self.allocate_sufficient_input_funds_from_lock(requested_amount, tip_digest) + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + self.allocate_sufficient_input_funds_from_lock(requested_amount, tip_digest, now) .await } + + pub async fn get_all_own_coins_with_possible_timelocks(&self) -> Vec { + let monitored_utxos = self.wallet_db.monitored_utxos(); + let mut own_coins = vec![]; + + for (_i, mutxo) in monitored_utxos.iter() { + if mutxo.spent_in_block.is_some() + || mutxo.abandoned_at.is_some() + || mutxo.get_latest_membership_proof_entry().is_none() + || mutxo.confirmed_in_block.is_none() + { + continue; + } + let coin = CoinWithPossibleTimeLock { + amount: mutxo.utxo.get_native_currency_amount(), + confirmed: mutxo.confirmed_in_block.unwrap().1, + release_date: mutxo.utxo.release_date(), + }; + own_coins.push(coin); + } + own_coins + } } #[cfg(test)] mod tests { use num_traits::One; + use rand::{thread_rng, Rng}; use tracing_test::traced_test; use crate::{ config_models::network::Network, - tests::shared::{get_mock_global_state, make_mock_block}, + tests::shared::{get_mock_global_state, get_mock_wallet_state, make_mock_block}, }; use super::*; @@ -735,6 +756,7 @@ mod tests { #[tokio::test] #[traced_test] async fn wallet_state_prune_abandoned_mutxos() { + let mut rng = thread_rng(); // Get genesis block. Verify wallet is empty // Add two blocks to state containing no UTXOs for own wallet // Add a UTXO (e.g. coinbase) in block 3a (height = 3) @@ -752,8 +774,7 @@ mod tests { let network = Network::Testnet; 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, Some(own_wallet_secret)).await; + 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 monitored_utxos_count_init = own_global_state @@ -778,7 +799,7 @@ mod tests { let mut latest_block = genesis_block; for _ in 1..=2 { let (new_block, _new_block_coinbase_utxo, _new_block_coinbase_sender_randomness) = - make_mock_block(&latest_block, None, other_recipient_address); + make_mock_block(&latest_block, None, other_recipient_address, rng.gen()); own_global_state .wallet_state .update_wallet_state_with_new_block(&mutator_set_accumulator, &new_block) @@ -818,7 +839,12 @@ mod tests { // Add block 3a with a coinbase UTXO for us let own_recipient_address = own_spending_key.to_address(); let (block_3a, block_3a_coinbase_utxo, block_3a_coinbase_sender_randomness) = - make_mock_block(&latest_block.clone(), None, own_recipient_address); + make_mock_block( + &latest_block.clone(), + None, + own_recipient_address, + rng.gen(), + ); own_global_state .wallet_state .expected_utxos @@ -875,7 +901,7 @@ mod tests { // Fork the blockchain with 3b, with no coinbase for us let (block_3b, _block_3b_coinbase_utxo, _block_3b_coinbase_sender_randomness) = - make_mock_block(&latest_block, None, other_recipient_address); + make_mock_block(&latest_block, None, other_recipient_address, rng.gen()); own_global_state .wallet_state .update_wallet_state_with_new_block(&mutator_set_accumulator, &block_3b) @@ -921,7 +947,7 @@ mod tests { mutator_set_accumulator = latest_block.kernel.body.mutator_set_accumulator.clone(); for _ in 4..=11 { let (new_block, _new_block_coinbase_utxo, _new_block_coinbase_sender_randomness) = - make_mock_block(&latest_block, None, other_recipient_address); + make_mock_block(&latest_block, None, other_recipient_address, rng.gen()); own_global_state .wallet_state .update_wallet_state_with_new_block(&mutator_set_accumulator, &new_block) @@ -966,7 +992,8 @@ mod tests { ); // Mine *one* more block. Verify that MUTXO is pruned - let (block_12, _, _) = make_mock_block(&latest_block, None, other_recipient_address); + let (block_12, _, _) = + make_mock_block(&latest_block, None, other_recipient_address, rng.gen()); own_global_state .wallet_state .update_wallet_state_with_new_block(&mutator_set_accumulator, &block_12) @@ -1021,4 +1048,45 @@ mod tests { "Latest balance height must be None at height 12" ); } + + #[traced_test] + #[tokio::test] + 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 wallet_state = get_mock_wallet_state(wallet, network).await; + + // are we synchronized to the genesis block? + assert_eq!( + wallet_state.wallet_db.get_sync_label(), + genesis_block.hash() + ); + + // Do we have valid membership proofs for all UTXOs received in the genesis block? + let monitored_utxos = wallet_state.wallet_db.monitored_utxos(); + let num_monitored_utxos = monitored_utxos.len(); + assert!(num_monitored_utxos > 0); + for i in 0..num_monitored_utxos { + let monitored_utxo: MonitoredUtxo = monitored_utxos.get(i); + if let Some((digest, _duration, _height)) = monitored_utxo.confirmed_in_block { + assert_eq!(digest, genesis_block.hash()); + } else { + panic!(); + } + let utxo = monitored_utxo.utxo; + let ms_membership_proof = monitored_utxo + .blockhash_to_membership_proof + .iter() + .find(|(bh, _mp)| *bh == genesis_block.hash()) + .unwrap() + .1 + .clone(); + assert!(genesis_block + .body() + .mutator_set_accumulator + .verify(Hash::hash(&utxo), &ms_membership_proof)); + } + } } diff --git a/src/models/state/wallet/wallet_status.rs b/src/models/state/wallet/wallet_status.rs index 7cf315a03..f7283f714 100644 --- a/src/models/state/wallet/wallet_status.rs +++ b/src/models/state/wallet/wallet_status.rs @@ -1,4 +1,5 @@ use std::fmt::Display; +use std::time::{SystemTime, UNIX_EPOCH}; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -8,36 +9,105 @@ use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; use crate::util_types::mutator_set::ms_membership_proof::MsMembershipProof; #[derive(Clone, Debug, Deserialize, Serialize)] -pub struct WalletStatusElement(pub u64, pub Utxo); +pub struct WalletStatusElement { + pub aocl_leaf_index: u64, + pub utxo: Utxo, +} + +impl WalletStatusElement { + pub fn new(aocl_leaf_index: u64, utxo: Utxo) -> Self { + Self { + aocl_leaf_index, + utxo, + } + } +} impl Display for WalletStatusElement { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let string: String = format!("({}, {:?})", self.0, self.1); + let string: String = format!("({}, {:?})", self.aocl_leaf_index, self.utxo); write!(f, "{}", string) } } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct WalletStatus { - pub synced_unspent_amount: NeptuneCoins, pub synced_unspent: Vec<(WalletStatusElement, MsMembershipProof)>, - pub unsynced_unspent_amount: NeptuneCoins, pub unsynced_unspent: Vec, - pub synced_spent_amount: NeptuneCoins, pub synced_spent: Vec, - pub unsynced_spent_amount: NeptuneCoins, pub unsynced_spent: Vec, } +impl WalletStatus { + pub fn synced_unspent_available_amount(&self, timestamp: u64) -> NeptuneCoins { + self.synced_unspent + .iter() + .map(|(wse, _msmp)| &wse.utxo) + .filter(|utxo| utxo.can_spend_at(timestamp)) + .map(|utxo| utxo.get_native_currency_amount()) + .sum::() + } + pub fn synced_unspent_timelocked_amount(&self, timestamp: u64) -> NeptuneCoins { + self.synced_unspent + .iter() + .map(|(wse, _msmp)| &wse.utxo) + .filter(|utxo| utxo.is_timelocked_but_otherwise_spendable_at(timestamp)) + .map(|utxo| utxo.get_native_currency_amount()) + .sum::() + } + pub fn unsynced_unspent_amount(&self) -> NeptuneCoins { + self.unsynced_unspent + .iter() + .map(|wse| wse.utxo.get_native_currency_amount()) + .sum::() + } + pub fn synced_spent_amount(&self) -> NeptuneCoins { + self.synced_spent + .iter() + .map(|wse| wse.utxo.get_native_currency_amount()) + .sum::() + } + pub fn unsynced_spent_amount(&self) -> NeptuneCoins { + self.unsynced_spent + .iter() + .map(|wse| wse.utxo.get_native_currency_amount()) + .sum::() + } +} + impl Display for WalletStatus { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let synced_unspent_count: usize = self.synced_unspent.len(); - let synced_unspent: String = format!( - "synced, unspent UTXOS: count: {}, amount: {:?}\n[{}]", - synced_unspent_count, - self.synced_unspent_amount, + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; + let synced_unspent_available_count: usize = self + .synced_unspent + .iter() + .filter(|(wse, _mnmp)| wse.utxo.can_spend_at(now)) + .count(); + let synced_unspent_available: String = format!( + "synced, unspent available UTXOS: count: {}, amount: {:?}\n[{}]", + synced_unspent_available_count, + self.synced_unspent_available_amount(now), + self.synced_unspent + .iter() + .filter(|(wse, _mnmp)| wse.utxo.can_spend_at(now)) + .map(|x| x.0.to_string()) + .join(",") + ); + let synced_unspent_timelocked_count: usize = self + .synced_unspent + .iter() + .filter(|(wse, _mnmp)| wse.utxo.is_timelocked_but_otherwise_spendable_at(now)) + .count(); + let synced_unspent_timelocked: String = format!( + "synced, unspent timelocked UTXOS: count: {}, amount: {:?}\n[{}]", + synced_unspent_timelocked_count, + self.synced_unspent_timelocked_amount(now), self.synced_unspent .iter() + .filter(|(wse, _mnmp)| wse.utxo.is_timelocked_but_otherwise_spendable_at(now)) .map(|x| x.0.to_string()) .join(",") ); @@ -45,7 +115,7 @@ impl Display for WalletStatus { let unsynced_unspent: String = format!( "unsynced, unspent UTXOS: count: {}, amount: {:?}\n[{}]", unsynced_unspent_count, - self.unsynced_unspent_amount, + self.unsynced_unspent_amount(), self.unsynced_unspent .iter() .map(|x| x.to_string()) @@ -55,20 +125,24 @@ impl Display for WalletStatus { let synced_spent: String = format!( "synced, spent UTXOS: count: {}, amount: {:?}\n[{}]", synced_spent_count, - self.synced_spent_amount, + self.synced_spent_amount(), self.synced_spent.iter().map(|x| x.to_string()).join(",") ); let unsynced_spent_count: usize = self.unsynced_spent.len(); let unsynced_spent: String = format!( "unsynced, spent UTXOS: count: {}, amount: {:?}\n[{}]", unsynced_spent_count, - self.unsynced_spent_amount, + self.unsynced_spent_amount(), self.unsynced_spent.iter().map(|x| x.to_string()).join(",") ); write!( f, - "{}\n\n{}\n\n{}\n\n{}", - synced_unspent, unsynced_unspent, synced_spent, unsynced_spent + "{}\n\n{}\n\n{}\n\n{}\n\n{}", + synced_unspent_available, + synced_unspent_timelocked, + unsynced_unspent, + synced_spent, + unsynced_spent ) } } diff --git a/src/peer_loop.rs b/src/peer_loop.rs index 73737032c..12bd02054 100644 --- a/src/peer_loop.rs +++ b/src/peer_loop.rs @@ -20,7 +20,7 @@ use itertools::Itertools; use std::cmp; use std::marker::Unpin; use std::net::SocketAddr; -use std::time::SystemTime; +use std::time::{SystemTime, UNIX_EPOCH}; use tokio::select; use tokio::sync::{broadcast, mpsc}; use tracing::{debug, error, info, warn}; @@ -113,6 +113,7 @@ impl PeerLoopHandler { "blocks" } ); + let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap(); let mut previous_block = &parent_of_first_block; for new_block in received_blocks.iter() { if !new_block.has_proof_of_work(previous_block) { @@ -132,7 +133,7 @@ impl PeerLoopHandler { ))) .await?; bail!("Failed to validate block due to insufficient PoW"); - } else if !new_block.is_valid(previous_block) { + } else if !new_block.is_valid(previous_block, now) { warn!( "Received invalid block of height {} from peer with IP {}", new_block.kernel.header.height, self.peer_address @@ -1175,6 +1176,7 @@ impl PeerLoopHandler { #[cfg(test)] mod peer_loop_tests { + use rand::{thread_rng, Rng}; use tokio::sync::mpsc::error::TryRecvError; use tracing_test::traced_test; @@ -1267,6 +1269,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn different_genesis_test() -> Result<()> { + let mut rng = thread_rng(); // In this scenario a peer provides another genesis block than what has been // hardcoded. This should lead to the closing of the connection to this peer // and a ban. @@ -1287,8 +1290,12 @@ mod peer_loop_tests { different_genesis_block.kernel.header.nonce[2].increment(); let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); - let (block_1_with_different_genesis, _, _) = - make_mock_block_with_valid_pow(&different_genesis_block, None, a_recipient_address); + let (block_1_with_different_genesis, _, _) = make_mock_block_with_valid_pow( + &different_genesis_block, + None, + a_recipient_address, + rng.gen(), + ); let mock = Mock::new(vec![Action::Read(PeerMessage::Block(Box::new( block_1_with_different_genesis.into(), )))]); @@ -1346,6 +1353,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn block_without_valid_pow_test() -> Result<()> { + let mut rng = thread_rng(); // In this scenario, a block without a valid PoW is received. This block should be rejected // by the peer loop and a notification should never reach the main loop. let network = Network::Alpha; @@ -1365,7 +1373,7 @@ mod peer_loop_tests { let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (block_without_valid_pow, _, _) = - make_mock_block_with_invalid_pow(&genesis_block, None, a_recipient_address); + make_mock_block_with_invalid_pow(&genesis_block, None, a_recipient_address, rng.gen()); // Sending an invalid block will not neccessarily result in a ban. This depends on the peer // tolerance that is set in the client. For this reason, we include a "Bye" here. @@ -1436,6 +1444,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_peer_loop_block_with_block_in_db() -> Result<()> { + let mut rng = thread_rng(); // The scenario tested here is that a client receives a block that is already // known and stored. The expected behavior is to ignore the block and not send // a message to the main thread. @@ -1453,7 +1462,7 @@ mod peer_loop_tests { let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); + make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); add_block(&mut global_state_mut, block_1.clone()).await?; drop(global_state_mut); @@ -1501,6 +1510,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn block_request_batch_in_order_test() -> Result<()> { + let mut rng = thread_rng(); // Scenario: A fork began at block 2, node knows two blocks of height 2 and two of height 3. // A peer requests a batch of blocks starting from block 1. Ensure that the correct blocks // are returned. @@ -1517,13 +1527,15 @@ mod peer_loop_tests { let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); - let (block_2_a, _, _) = make_mock_block_with_valid_pow(&block_1, None, a_recipient_address); + make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); + let (block_2_a, _, _) = + make_mock_block_with_valid_pow(&block_1, None, a_recipient_address, rng.gen()); let (block_3_a, _, _) = - make_mock_block_with_valid_pow(&block_2_a, None, a_recipient_address); // <--- canonical - let (block_2_b, _, _) = make_mock_block_with_valid_pow(&block_1, None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2_a, None, a_recipient_address, rng.gen()); // <--- canonical + let (block_2_b, _, _) = + make_mock_block_with_valid_pow(&block_1, None, a_recipient_address, rng.gen()); let (block_3_b, _, _) = - make_mock_block_with_valid_pow(&block_2_b, None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2_b, None, a_recipient_address, rng.gen()); add_block(&mut global_state_mut, block_1.clone()).await?; add_block(&mut global_state_mut, block_2_a.clone()).await?; @@ -1591,6 +1603,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn block_request_batch_out_of_order_test() -> Result<()> { + let mut rng = thread_rng(); // Scenario: Same as above, but the peer supplies their hashes in a wrong order. // Ensure that the correct blocks are returned, in the right order. let network = Network::Alpha; @@ -1606,13 +1619,15 @@ mod peer_loop_tests { let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); - let (block_2_a, _, _) = make_mock_block_with_valid_pow(&block_1, None, a_recipient_address); + make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); + let (block_2_a, _, _) = + make_mock_block_with_valid_pow(&block_1, None, a_recipient_address, rng.gen()); let (block_3_a, _, _) = - make_mock_block_with_valid_pow(&block_2_a, None, a_recipient_address); // <--- canonical - let (block_2_b, _, _) = make_mock_block_with_valid_pow(&block_1, None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2_a, None, a_recipient_address, rng.gen()); // <--- canonical + let (block_2_b, _, _) = + make_mock_block_with_valid_pow(&block_1, None, a_recipient_address, rng.gen()); let (block_3_b, _, _) = - make_mock_block_with_valid_pow(&block_2_b, None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2_b, None, a_recipient_address, rng.gen()); add_block(&mut global_state_mut, block_1.clone()).await?; add_block(&mut global_state_mut, block_2_a.clone()).await?; @@ -1654,6 +1669,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn find_canonical_chain_when_multiple_blocks_at_same_height_test() -> Result<()> { + let mut rng = thread_rng(); // Scenario: A fork began at block 2, node knows two blocks of height 2 and two of height 3. // A peer requests a block at height 2. Verify that the correct block at height 2 is returned. let network = Network::Alpha; @@ -1669,13 +1685,15 @@ mod peer_loop_tests { let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); - let (block_2_a, _, _) = make_mock_block_with_valid_pow(&block_1, None, a_recipient_address); + make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); + let (block_2_a, _, _) = + make_mock_block_with_valid_pow(&block_1, None, a_recipient_address, rng.gen()); let (block_3_a, _, _) = - make_mock_block_with_valid_pow(&block_2_a, None, a_recipient_address); // <--- canonical - let (block_2_b, _, _) = make_mock_block_with_valid_pow(&block_1, None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2_a, None, a_recipient_address, rng.gen()); // <--- canonical + let (block_2_b, _, _) = + make_mock_block_with_valid_pow(&block_1, None, a_recipient_address, rng.gen()); let (block_3_b, _, _) = - make_mock_block_with_valid_pow(&block_2_b, None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2_b, None, a_recipient_address, rng.gen()); add_block(&mut global_state_mut, block_1.clone()).await?; add_block(&mut global_state_mut, block_2_a.clone()).await?; @@ -1714,6 +1732,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_peer_loop_receival_of_first_block() -> Result<()> { + 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?; @@ -1729,7 +1748,7 @@ mod peer_loop_tests { .await; let (mock_block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); + make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); let mock = Mock::new(vec![ Action::Read(PeerMessage::Block(Box::new(mock_block_1.into()))), Action::Read(PeerMessage::Bye), @@ -1774,6 +1793,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_peer_loop_receival_of_second_block_no_blocks_in_db() -> Result<()> { + 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; @@ -1790,9 +1810,9 @@ mod peer_loop_tests { let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address); + make_mock_block_with_valid_pow(&genesis_block, None, a_recipient_address, rng.gen()); let (block_2, _, _) = - make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address, rng.gen()); let mock = Mock::new(vec![ Action::Read(PeerMessage::Block(Box::new(block_2.clone().into()))), @@ -1845,6 +1865,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn prevent_ram_exhaustion_test() -> Result<()> { + let mut rng = thread_rng(); // 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. @@ -1875,14 +1896,30 @@ mod peer_loop_tests { .wallet_secret .nth_generation_spending_key(0) .to_address(); - let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block.clone(), None, own_recipient_address); - let (block_2, _, _) = - make_mock_block_with_valid_pow(&block_1.clone(), None, own_recipient_address); - let (block_3, _, _) = - make_mock_block_with_valid_pow(&block_2.clone(), None, own_recipient_address); - let (block_4, _, _) = - make_mock_block_with_valid_pow(&block_3.clone(), None, own_recipient_address); + let (block_1, _, _) = make_mock_block_with_valid_pow( + &genesis_block.clone(), + None, + own_recipient_address, + rng.gen(), + ); + let (block_2, _, _) = make_mock_block_with_valid_pow( + &block_1.clone(), + None, + own_recipient_address, + rng.gen(), + ); + let (block_3, _, _) = make_mock_block_with_valid_pow( + &block_2.clone(), + None, + own_recipient_address, + rng.gen(), + ); + let (block_4, _, _) = make_mock_block_with_valid_pow( + &block_3.clone(), + None, + own_recipient_address, + rng.gen(), + ); add_block(&mut global_state_mut, block_1.clone()).await?; drop(global_state_mut); @@ -1940,6 +1977,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_peer_loop_receival_of_fourth_block_one_block_in_db() -> Result<()> { + 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; @@ -1954,14 +1992,18 @@ mod peer_loop_tests { .await; let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); - let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block.clone(), None, a_recipient_address); + let (block_1, _, _) = make_mock_block_with_valid_pow( + &genesis_block.clone(), + None, + a_recipient_address, + rng.gen(), + ); let (block_2, _, _) = - make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address, rng.gen()); let (block_3, _, _) = - make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address, rng.gen()); let (block_4, _, _) = - make_mock_block_with_valid_pow(&block_3.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_3.clone(), None, a_recipient_address, rng.gen()); add_block(&mut global_state_mut, block_1.clone()).await?; drop(global_state_mut); @@ -2021,6 +2063,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_peer_loop_receival_of_third_block_no_blocks_in_db() -> Result<()> { + let mut rng = thread_rng(); // In this scenario, the client only knows the genesis block (block 0) and then // receives block 3, meaning that block 2 and 1 will have to be requested. let network = Network::RegTest; @@ -2032,12 +2075,16 @@ mod peer_loop_tests { let genesis_block: Block = global_state.chain.archival_state().get_latest_block().await; let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); - let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block.clone(), None, a_recipient_address); + let (block_1, _, _) = make_mock_block_with_valid_pow( + &genesis_block.clone(), + None, + a_recipient_address, + rng.gen(), + ); let (block_2, _, _) = - make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address, rng.gen()); let (block_3, _, _) = - make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address, rng.gen()); drop(global_state); let mock = Mock::new(vec![ @@ -2096,6 +2143,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_block_reconciliation_interrupted_by_block_notification() -> Result<()> { + 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, 2, and 1 will have to be requested. // But the requests are interrupted by the peer sending another message: a new block @@ -2112,16 +2160,20 @@ mod peer_loop_tests { .archival_state() .get_latest_block() .await; - let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block.clone(), None, a_recipient_address); + let (block_1, _, _) = make_mock_block_with_valid_pow( + &genesis_block.clone(), + None, + a_recipient_address, + rng.gen(), + ); let (block_2, _, _) = - make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address, rng.gen()); let (block_3, _, _) = - make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address, rng.gen()); let (block_4, _, _) = - make_mock_block_with_valid_pow(&block_3.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_3.clone(), None, a_recipient_address, rng.gen()); let (block_5, _, _) = - make_mock_block_with_valid_pow(&block_4.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_4.clone(), None, a_recipient_address, rng.gen()); add_block(&mut global_state_mut, block_1.clone()).await?; drop(global_state_mut); @@ -2196,6 +2248,7 @@ mod peer_loop_tests { #[traced_test] #[tokio::test] async fn test_block_reconciliation_interrupted_by_peer_list_request() -> Result<()> { + let mut rng = thread_rng(); // In this scenario, the client knows the genesis block (block 0) and block 1, it // then receives block 4, meaning that block 3, 2, and 1 will have to be requested. // But the requests are interrupted by the peer sending another message: a request @@ -2218,14 +2271,18 @@ mod peer_loop_tests { .await; let a_wallet_secret = WalletSecret::new_random(); let a_recipient_address = a_wallet_secret.nth_generation_spending_key(0).to_address(); - let (block_1, _, _) = - make_mock_block_with_valid_pow(&genesis_block.clone(), None, a_recipient_address); + let (block_1, _, _) = make_mock_block_with_valid_pow( + &genesis_block.clone(), + None, + a_recipient_address, + rng.gen(), + ); let (block_2, _, _) = - make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_1.clone(), None, a_recipient_address, rng.gen()); let (block_3, _, _) = - make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_2.clone(), None, a_recipient_address, rng.gen()); let (block_4, _, _) = - make_mock_block_with_valid_pow(&block_3.clone(), None, a_recipient_address); + make_mock_block_with_valid_pow(&block_3.clone(), None, a_recipient_address, rng.gen()); add_block(&mut global_state_mut, block_1.clone()).await?; drop(global_state_mut); diff --git a/src/rpc_server.rs b/src/rpc_server.rs index 335300d49..c2ea18b6e 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::state::wallet::coin_with_possible_timelock::CoinWithPossibleTimeLock; use crate::prelude::twenty_first; use anyhow::Result; @@ -9,6 +10,8 @@ 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}; @@ -32,7 +35,8 @@ use crate::models::state::{GlobalStateLock, UtxoReceiverData}; pub struct DashBoardOverviewDataFromClient { pub tip_header: BlockHeader, pub syncing: bool, - pub synced_balance: NeptuneCoins, + pub available_balance: NeptuneCoins, + pub timelocked_balance: NeptuneCoins, pub mempool_size: usize, pub mempool_tx_count: usize, @@ -122,6 +126,9 @@ pub trait RPC { /// Determine whether the given amount is less than (or equal to) the balance async fn amount_leq_synced_balance(amount: NeptuneCoins) -> bool; + /// Generate a report of all owned and unspent coins, whether time-locked or not. + async fn list_own_coins() -> Vec; + /******** CHANGE THINGS ********/ // Place all things that change state here @@ -303,6 +310,10 @@ 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; // test inequality let wallet_status = self .state @@ -310,17 +321,21 @@ impl RPC for NeptuneRPCServer { .await .get_wallet_status_for_tip() .await; - amount <= wallet_status.synced_unspent_amount + amount <= wallet_status.synced_unspent_available_amount(now) } async fn synced_balance(self, _context: tarpc::context::Context) -> NeptuneCoins { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; let wallet_status = self .state .lock_guard() .await .get_wallet_status_for_tip() .await; - wallet_status.synced_unspent_amount + wallet_status.synced_unspent_available_amount(now) } async fn wallet_status(self, _context: tarpc::context::Context) -> WalletStatus { @@ -398,6 +413,10 @@ impl RPC for NeptuneRPCServer { self, _context: tarpc::context::Context, ) -> DashBoardOverviewDataFromClient { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as u64; 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; @@ -415,7 +434,8 @@ impl RPC for NeptuneRPCServer { DashBoardOverviewDataFromClient { tip_header, syncing, - synced_balance: wallet_status.synced_unspent_amount, + available_balance: wallet_status.synced_unspent_available_amount(now), + timelocked_balance: wallet_status.synced_unspent_timelocked_amount(now), mempool_size, mempool_tx_count, peer_count, @@ -483,6 +503,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 state = self.state.lock_guard().await; let block_height = state.chain.light_state().header().height; @@ -528,7 +549,7 @@ impl RPC for NeptuneRPCServer { .state .lock_guard_mut() .await - .create_transaction(receiver_data, fee) + .create_transaction(receiver_data, fee, now) .await; let transaction = match transaction_result { @@ -619,6 +640,19 @@ impl RPC for NeptuneRPCServer { } } } + + #[doc = r" Generate a report of all owned and unspent coins, whether time-locked or not."] + async fn list_own_coins( + self, + _context: ::tarpc::context::Context, + ) -> Vec { + self.state + .lock_guard() + .await + .wallet_state + .get_all_own_coins_with_possible_timelocks() + .await + } } #[cfg(test)] @@ -642,8 +676,7 @@ mod rpc_server_tests { wallet_secret: WalletSecret, peer_count: u8, ) -> (NeptuneRPCServer, GlobalStateLock) { - let global_state_lock = - get_mock_global_state(network, peer_count, Some(wallet_secret)).await; + let global_state_lock = get_mock_global_state(network, peer_count, wallet_secret).await; let (dummy_tx, _rx) = tokio::sync::mpsc::channel::(RPC_CHANNEL_CAPACITY); ( NeptuneRPCServer { diff --git a/src/tests/shared.rs b/src/tests/shared.rs index 7152b36c4..966a9d0bd 100644 --- a/src/tests/shared.rs +++ b/src/tests/shared.rs @@ -1,6 +1,8 @@ +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::ValidityTree; use crate::prelude::twenty_first; use anyhow::Result; @@ -42,7 +44,6 @@ use twenty_first::util_types::algebraic_hasher::AlgebraicHasher; use twenty_first::util_types::mmr::mmr_trait::Mmr; use twenty_first::shared_math::b_field_element::BFieldElement; -use twenty_first::shared_math::other::random_elements_array; use crate::config_models::cli_args; use crate::config_models::data_directory::DataDirectory; @@ -60,7 +61,6 @@ use crate::models::blockchain::transaction::transaction_kernel::TransactionKerne use crate::models::blockchain::transaction::validity::removal_records_integrity::RemovalRecordsIntegrityWitness; use crate::models::blockchain::transaction::validity::TransactionValidationLogic; use crate::models::blockchain::transaction::PublicAnnouncement; -use crate::models::blockchain::transaction::TransactionWitness; use crate::models::blockchain::transaction::{utxo::Utxo, Transaction}; use crate::models::blockchain::type_scripts::TypeScript; use crate::models::channel::{MainToPeerThread, PeerThreadToMain}; @@ -194,7 +194,7 @@ pub fn get_dummy_peer_connection_data_genesis( pub async fn get_mock_global_state( network: Network, peer_count: u8, - wallet: Option, + wallet: WalletSecret, ) -> GlobalStateLock { let (archival_state, peer_db, _data_dir) = make_unit_test_archival_state(network).await; @@ -207,7 +207,7 @@ pub async fn get_mock_global_state( } let networking_state = NetworkingState::new(peer_map, peer_db, syncing); let (block, _, _) = get_dummy_latest_block(None); - let light_state: LightState = LightState::from(block); + let light_state: LightState = LightState::from(block.clone()); let blockchain_state = BlockchainState::Archival(BlockchainArchivalState { light_state, archival_state, @@ -218,8 +218,10 @@ pub async fn get_mock_global_state( ..Default::default() }; + let wallet_state = get_mock_wallet_state(wallet, network).await; + GlobalStateLock::new( - get_mock_wallet_state(wallet, network).await, + wallet_state, blockchain_state, networking_state, cli_args.clone(), @@ -249,7 +251,8 @@ pub async fn get_test_genesis_setup( let (to_main_tx, mut _to_main_rx1) = mpsc::channel::(PEER_CHANNEL_CAPACITY); let from_main_rx_clone = peer_broadcast_tx.subscribe(); - let state = get_mock_global_state(network, peer_count, None).await; + let devnet_wallet = WalletSecret::devnet_wallet(); + let state = get_mock_global_state(network, peer_count, devnet_wallet).await; Ok(( peer_broadcast_tx, from_main_rx_clone, @@ -800,7 +803,7 @@ pub fn make_mock_transaction_with_generation_key( .map(|(_utxo, _mp, sk)| sk.to_address().lock_script()) .collect_vec(); let output_utxos = receiver_data.into_iter().map(|rd| rd.utxo).collect(); - let primitive_witness = PrimitiveWitness { + let primitive_witness = transaction::primitive_witness::PrimitiveWitness { input_utxos: SaltedUtxos::new(input_utxos), type_scripts, input_lock_scripts, @@ -810,11 +813,11 @@ pub fn make_mock_transaction_with_generation_key( mutator_set_accumulator: tip_msa, kernel: kernel.clone(), }; - let validity_logic = TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); + let validity_logic = TransactionValidationLogic::from(primitive_witness); Transaction { kernel, - witness: TransactionWitness::ValidationLogic(validity_logic), + witness: validity_logic, } } @@ -843,7 +846,10 @@ pub fn make_mock_transaction( coinbase: None, mutator_set_hash: random(), }, - witness: TransactionWitness::Faith, + witness: TransactionValidationLogic { + vast: ValidityTree::axiom(), + maybe_primitive_witness: None, + }, } } @@ -878,7 +884,10 @@ pub fn make_mock_transaction_with_wallet( Transaction { kernel, - witness: TransactionWitness::Faith, + witness: TransactionValidationLogic { + vast: ValidityTree::axiom(), + maybe_primitive_witness: None, + }, } } @@ -891,14 +900,16 @@ pub fn make_mock_block( // target_difficulty: Option>, block_timestamp: Option, coinbase_beneficiary: generation_address::ReceivingAddress, + seed: [u8; 32], ) -> (Block, Utxo, Digest) { + let mut rng: StdRng = SeedableRng::from_seed(seed); let new_block_height: BlockHeight = previous_block.kernel.header.height.next(); // Build coinbase UTXO and associated data let lock_script = coinbase_beneficiary.lock_script(); let coinbase_amount = Block::get_mining_reward(new_block_height); let coinbase_utxo = Utxo::new(lock_script, coinbase_amount.to_native_coins()); - let coinbase_output_randomness: Digest = Digest::new(random_elements_array()); + let coinbase_output_randomness: Digest = rng.gen(); let receiver_digest: Digest = coinbase_beneficiary.privacy_digest; let mut next_mutator_set = previous_block.kernel.body.mutator_set_accumulator.clone(); @@ -936,11 +947,11 @@ pub fn make_mock_block( input_lock_scripts: vec![], kernel: tx_kernel.clone(), }; - let validation_logic = - TransactionValidationLogic::new_from_primitive_witness(&primitive_witness); + let mut validation_logic = TransactionValidationLogic::from(primitive_witness); + validation_logic.vast.prove(); let transaction = Transaction { - witness: TransactionWitness::ValidationLogic(validation_logic), + witness: validation_logic, kernel: tx_kernel, }; @@ -980,12 +991,22 @@ pub fn make_mock_block_with_valid_pow( previous_block: &Block, block_timestamp: Option, coinbase_beneficiary: generation_address::ReceivingAddress, + seed: [u8; 32], ) -> (Block, Utxo, Digest) { - let (mut block, mut utxo, mut digest) = - make_mock_block(previous_block, block_timestamp, coinbase_beneficiary); + let mut rng: StdRng = SeedableRng::from_seed(seed); + let (mut block, mut utxo, mut digest) = make_mock_block( + previous_block, + block_timestamp, + coinbase_beneficiary, + rng.gen(), + ); while !block.has_proof_of_work(previous_block) { - let (block_new, utxo_new, digest_new) = - make_mock_block(previous_block, block_timestamp, coinbase_beneficiary); + let (block_new, utxo_new, digest_new) = make_mock_block( + previous_block, + block_timestamp, + coinbase_beneficiary, + rng.gen(), + ); block = block_new; utxo = utxo_new; digest = digest_new; @@ -997,12 +1018,22 @@ pub fn make_mock_block_with_invalid_pow( previous_block: &Block, block_timestamp: Option, coinbase_beneficiary: generation_address::ReceivingAddress, + seed: [u8; 32], ) -> (Block, Utxo, Digest) { - let (mut block, mut utxo, mut digest) = - make_mock_block(previous_block, block_timestamp, coinbase_beneficiary); + let mut rng: StdRng = SeedableRng::from_seed(seed); + let (mut block, mut utxo, mut digest) = make_mock_block( + previous_block, + block_timestamp, + coinbase_beneficiary, + rng.gen(), + ); while block.has_proof_of_work(previous_block) { - let (block_new, utxo_new, digest_new) = - make_mock_block(previous_block, block_timestamp, coinbase_beneficiary); + let (block_new, utxo_new, digest_new) = make_mock_block( + previous_block, + block_timestamp, + coinbase_beneficiary, + rng.gen(), + ); block = block_new; utxo = utxo_new; digest = digest_new; @@ -1012,21 +1043,13 @@ pub fn make_mock_block_with_invalid_pow( /// Return a dummy-wallet used for testing. The returned wallet is populated with /// whatever UTXOs are present in the genesis block. -pub async fn get_mock_wallet_state( - maybe_wallet_secret: Option, - network: Network, -) -> WalletState { - let wallet_secret = match maybe_wallet_secret { - Some(wallet) => wallet, - None => WalletSecret::devnet_wallet(), - }; - +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, ..Default::default() }; let data_dir = unit_test_data_directory(network).unwrap(); - WalletState::new_from_wallet_secret(&data_dir, wallet_secret, &cli_args).await + WalletState::new_from_wallet_secret(&data_dir, wallet_secret.clone(), &cli_args).await } pub async fn make_unit_test_archival_state( diff --git a/src/util_types/mutator_set/mutator_set_kernel.rs b/src/util_types/mutator_set/mutator_set_kernel.rs index e7c7e755a..ab1a72562 100644 --- a/src/util_types/mutator_set/mutator_set_kernel.rs +++ b/src/util_types/mutator_set/mutator_set_kernel.rs @@ -178,10 +178,11 @@ impl> MutatorSetKernel { .get_mut(&chunk_index) .unwrap_or_else(|| { panic!( - "Can't get chunk index {chunk_index} from dictionary! dictionary: {:?}\nAOCL size: {}\nbatch index: {}", + "Can't get chunk index {chunk_index} from removal record dictionary! dictionary: {:?}\nAOCL size: {}\nbatch index: {}\nRemoval record: {:?}", new_target_chunks_clone.dictionary, self.aocl.count_leaves(), - batch_index + batch_index, + removal_record ) }); for index in indices { @@ -551,21 +552,8 @@ mod accumulation_scheme_tests { use rand::prelude::*; use rand::Rng; - use tasm_lib::twenty_first::util_types::storage_vec::StorageVec; use twenty_first::util_types::mmr::mmr_accumulator::MmrAccumulator; - use crate::config_models::network::Network; - use crate::models::blockchain::block::Block; - use crate::models::blockchain::transaction::utxo::Utxo; - use crate::models::blockchain::transaction::PublicAnnouncement; - use crate::models::blockchain::type_scripts::neptune_coins::NeptuneCoins; - use crate::models::state::wallet::utxo_notification_pool::UtxoNotifier; - use crate::models::state::wallet::WalletSecret; - use crate::models::state::UtxoReceiverData; - use crate::tests::shared::add_block; - use crate::tests::shared::get_mock_global_state; - use crate::tests::shared::get_mock_wallet_state; - use crate::tests::shared::make_mock_block_with_valid_pow; use crate::util_types::mutator_set::mutator_set_accumulator::MutatorSetAccumulator; use crate::util_types::mutator_set::mutator_set_trait::commit; use crate::util_types::mutator_set::mutator_set_trait::MutatorSet; @@ -1117,303 +1105,4 @@ mod accumulation_scheme_tests { "Membership proof must fail after removal" ); } - - #[tokio::test] - async fn flaky_mutator_set_test() { - // let mut rng: StdRng = - // SeedableRng::from_rng(thread_rng()).expect("failure lifting thread_rng to StdRng"); - // let seed: [u8; 32] = rng.gen(); - let seed = [ - 0xf4, 0xc2, 0x1c, 0xd0, 0x5a, 0xac, 0x99, 0xe7, 0x3a, 0x1e, 0x29, 0x7f, 0x16, 0xc1, - 0x50, 0x5e, 0x1e, 0xd, 0x4b, 0x49, 0x51, 0x9c, 0x1b, 0xa0, 0x38, 0x3c, 0xd, 0x83, 0x29, - 0xdb, 0xab, 0xe2, - ]; - println!( - "seed: [{}]", - seed.iter().map(|h| format!("{:#x}", h)).join(", ") - ); - 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 genesis_wallet_state = get_mock_wallet_state(None, network).await; - let genesis_spending_key = genesis_wallet_state - .wallet_secret - .nth_generation_spending_key(0); - let genesis_state_lock = - get_mock_global_state(network, 3, Some(genesis_wallet_state.wallet_secret)).await; - - let wallet_secret_alice = WalletSecret::new_pseudorandom(rng.gen()); - let alice_spending_key = wallet_secret_alice.nth_generation_spending_key(0); - let alice_state_lock = get_mock_global_state(network, 3, Some(wallet_secret_alice)).await; - - let wallet_secret_bob = WalletSecret::new_pseudorandom(rng.gen()); - let bob_spending_key = wallet_secret_bob.nth_generation_spending_key(0); - let bob_state_lock = get_mock_global_state(network, 3, Some(wallet_secret_bob)).await; - - let genesis_block = Block::genesis_block(); - - let (mut block_1, cb_utxo, cb_output_randomness) = - make_mock_block_with_valid_pow(&genesis_block, None, genesis_spending_key.to_address()); - - // Send two outputs each to Alice and Bob, from genesis receiver - let fee = NeptuneCoins::one(); - let sender_randomness: Digest = rng.gen(); - let receiver_data_for_alice = vec![ - UtxoReceiverData { - public_announcement: PublicAnnouncement::default(), - receiver_privacy_digest: alice_spending_key.to_address().privacy_digest, - sender_randomness, - utxo: Utxo { - lock_script_hash: alice_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(41).to_native_coins(), - }, - }, - UtxoReceiverData { - public_announcement: PublicAnnouncement::default(), - receiver_privacy_digest: alice_spending_key.to_address().privacy_digest, - sender_randomness, - utxo: Utxo { - lock_script_hash: alice_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(59).to_native_coins(), - }, - }, - ]; - // Two outputs for Bob - let receiver_data_for_bob = vec![ - UtxoReceiverData { - public_announcement: PublicAnnouncement::default(), - receiver_privacy_digest: bob_spending_key.to_address().privacy_digest, - sender_randomness, - utxo: Utxo { - lock_script_hash: bob_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(141).to_native_coins(), - }, - }, - UtxoReceiverData { - public_announcement: PublicAnnouncement::default(), - receiver_privacy_digest: bob_spending_key.to_address().privacy_digest, - sender_randomness, - utxo: Utxo { - lock_script_hash: bob_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(59).to_native_coins(), - }, - }, - ]; - { - let tx_to_alice_and_bob = genesis_state_lock - .lock_guard_mut() - .await - .create_transaction( - [ - receiver_data_for_alice.clone(), - receiver_data_for_bob.clone(), - ] - .concat(), - fee, - ) - .await - .unwrap(); - - // Absorb and verify validity - block_1.accumulate_transaction( - tx_to_alice_and_bob, - &genesis_block.kernel.body.mutator_set_accumulator, - ); - assert!(block_1.is_valid(&genesis_block)); - } - - println!("Accumulated transaction into block_1."); - println!( - "Transaction has {} inputs (removal records) and {} outputs (addition records)", - block_1.kernel.body.transaction.kernel.inputs.len(), - block_1.kernel.body.transaction.kernel.outputs.len() - ); - - // Update chain states - for state_lock in [&genesis_state_lock, &alice_state_lock, &bob_state_lock] { - let mut state = state_lock.lock_guard_mut().await; - add_block(&mut state, block_1.clone()).await.unwrap(); - state - .chain - .archival_state_mut() - .update_mutator_set(&block_1) - .await - .unwrap(); - } - - { - // Update wallets - let mut genesis_state = genesis_state_lock.lock_guard_mut().await; - genesis_state - .wallet_state - .expected_utxos - .add_expected_utxo( - cb_utxo, - cb_output_randomness, - genesis_spending_key.privacy_preimage, - UtxoNotifier::OwnMiner, - ) - .unwrap(); - genesis_state - .wallet_state - .update_wallet_state_with_new_block( - &genesis_block.kernel.body.mutator_set_accumulator, - &block_1, - ) - .await - .unwrap(); - assert_eq!( - 3, - genesis_state - .wallet_state - .wallet_db - .monitored_utxos() - .len(), "Genesis receiver must have 3 UTXOs after block 1: change from transaction, coinbase from block 1, and the spent premine UTXO" - ); - } - - { - let mut alice_state = alice_state_lock.lock_guard_mut().await; - for rec_data in receiver_data_for_alice { - alice_state - .wallet_state - .expected_utxos - .add_expected_utxo( - rec_data.utxo.clone(), - rec_data.sender_randomness, - alice_spending_key.privacy_preimage, - UtxoNotifier::Cli, - ) - .unwrap(); - } - alice_state - .wallet_state - .update_wallet_state_with_new_block( - &genesis_block.kernel.body.mutator_set_accumulator, - &block_1, - ) - .await - .unwrap(); - } - - { - let mut bob_state = bob_state_lock.lock_guard_mut().await; - for rec_data in receiver_data_for_bob { - bob_state - .wallet_state - .expected_utxos - .add_expected_utxo( - rec_data.utxo.clone(), - rec_data.sender_randomness, - bob_spending_key.privacy_preimage, - UtxoNotifier::Cli, - ) - .unwrap(); - } - bob_state - .wallet_state - .update_wallet_state_with_new_block( - &genesis_block.kernel.body.mutator_set_accumulator, - &block_1, - ) - .await - .unwrap(); - } - - // Now Alice should have a balance of 100 and Bob a balance of 200 - - assert_eq!( - NeptuneCoins::new(100), - alice_state_lock - .lock_guard() - .await - .get_wallet_status_for_tip() - .await - .synced_unspent_amount - ); - assert_eq!( - NeptuneCoins::new(200), - bob_state_lock - .lock_guard() - .await - .get_wallet_status_for_tip() - .await - .synced_unspent_amount - ); - - // Make two transactions: Alice sends two UTXOs to Genesis and Bob sends three UTXOs to genesis - let receiver_data_from_alice = vec![ - UtxoReceiverData { - utxo: Utxo { - lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(50).to_native_coins(), - }, - sender_randomness: rng.gen(), - receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, - public_announcement: PublicAnnouncement::default(), - }, - UtxoReceiverData { - utxo: Utxo { - lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(49).to_native_coins(), - }, - sender_randomness: rng.gen(), - receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, - public_announcement: PublicAnnouncement::default(), - }, - ]; - let tx_from_alice = alice_state_lock - .lock_guard_mut() - .await - .create_transaction(receiver_data_from_alice.clone(), NeptuneCoins::new(1)) - .await - .unwrap(); - let receiver_data_from_bob = vec![ - UtxoReceiverData { - utxo: Utxo { - lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(50).to_native_coins(), - }, - sender_randomness: rng.gen(), - receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, - public_announcement: PublicAnnouncement::default(), - }, - UtxoReceiverData { - utxo: Utxo { - lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(50).to_native_coins(), - }, - sender_randomness: rng.gen(), - receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, - public_announcement: PublicAnnouncement::default(), - }, - UtxoReceiverData { - utxo: Utxo { - lock_script_hash: genesis_spending_key.to_address().lock_script().hash(), - coins: NeptuneCoins::new(98).to_native_coins(), - }, - sender_randomness: rng.gen(), - receiver_privacy_digest: genesis_spending_key.to_address().privacy_digest, - public_announcement: PublicAnnouncement::default(), - }, - ]; - let tx_from_bob = bob_state_lock - .lock_guard_mut() - .await - .create_transaction(receiver_data_from_bob.clone(), NeptuneCoins::new(2)) - .await - .unwrap(); - - // Make block_2 with tx that contains: - // - 4 inputs: 2 from Alice and 2 from Bob - // - 6 outputs: 2 from Alice to Genesis, 3 from Bob to Genesis, and 1 coinbase to Genesis - let (mut block_2, _cb_utxo_block_2, _cb_sender_randomness_block_2) = - make_mock_block_with_valid_pow(&block_1, None, genesis_spending_key.to_address()); - block_2.accumulate_transaction(tx_from_alice, &block_1.kernel.body.mutator_set_accumulator); - assert_eq!(2, block_2.kernel.body.transaction.kernel.inputs.len()); - assert_eq!(3, block_2.kernel.body.transaction.kernel.outputs.len()); - - block_2.accumulate_transaction(tx_from_bob, &block_1.kernel.body.mutator_set_accumulator); - } } diff --git a/src/util_types/mutator_set/removal_record.rs b/src/util_types/mutator_set/removal_record.rs index 8a2763197..08102ce73 100644 --- a/src/util_types/mutator_set/removal_record.rs +++ b/src/util_types/mutator_set/removal_record.rs @@ -165,7 +165,7 @@ impl RemovalRecord { mmra.append(new_chunk_digest); // Collect all indices for all removal records that are being updated - let mut chunk_index_to_mp_index: HashMap> = HashMap::new(); + let mut chunk_index_to_rr_index: HashMap> = HashMap::new(); removal_records.iter().enumerate().for_each(|(i, rr)| { let indices = &rr.absolute_indices; let chunks_set: HashSet = indices @@ -173,25 +173,27 @@ impl RemovalRecord { .iter() .map(|x| (x / CHUNK_SIZE as u128) as u64) .collect(); + chunks_set .iter() - .for_each(|chnkidx| chunk_index_to_mp_index.entry(*chnkidx).or_default().push(i)); + .for_each(|chnkidx| chunk_index_to_rr_index.entry(*chnkidx).or_default().push(i)); }); - // Find the removal records that need a new dictionary entry for the chunk that's being - // added to the inactive part by this addition. + // Find the removal records that need a new dictionary entry for the chunk + // that's being added to the inactive part by this addition. let batch_index = new_item_index / BATCH_SIZE as u64; let old_window_start_batch_index = batch_index - 1; + let rrs_for_new_chunk_dictionary_entry: Vec = - match chunk_index_to_mp_index.get(&old_window_start_batch_index) { + match chunk_index_to_rr_index.get(&old_window_start_batch_index) { Some(vals) => vals.clone(), None => vec![], }; - // Find the removal records that have dictionary entry MMR membership proofs that need - // to be updated because of the window sliding. + // Find the removal records that have dictionary entry MMR membership proofs + // that need to be updated because of the window sliding. let mut rrs_for_batch_append: HashSet = HashSet::new(); - for (chunk_index, mp_indices) in chunk_index_to_mp_index.into_iter() { + for (chunk_index, mp_indices) in chunk_index_to_rr_index.into_iter() { if chunk_index < old_window_start_batch_index { for mp_index in mp_indices { rrs_for_batch_append.insert(mp_index);