From 6f0ff684b87fe62cbfc69da988700c4bbba0095c Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Fri, 20 Sep 2024 15:44:31 +0200 Subject: [PATCH 1/2] Update the pre-mine specification - Updated the pre-mine specification to cater for different upfront release strategies - Updated pre-mine spend to take into account spending multiple outputs to a single wallet - Updated pre-mine spend to output immediate genesis block spends to json for inclusion in the genesis block. --- .../src/automation/commands.rs | 935 ++++++++++++------ .../src/automation/mod.rs | 44 +- .../minotari_console_wallet/src/cli.rs | 58 +- .../src/wallet_modes.rs | 7 +- base_layer/core/src/blocks/pre_mine/mod.rs | 869 +++++++++++++--- .../core/src/transactions/tari_amount.rs | 7 +- .../src/output_manager_service/handle.rs | 9 +- .../src/output_manager_service/service.rs | 7 +- .../wallet/src/transaction_service/handle.rs | 10 +- .../wallet/src/transaction_service/service.rs | 4 + 10 files changed, 1537 insertions(+), 413 deletions(-) diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 52ca61e09c..8d403f3410 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::{ + cmp::{max, min}, collections::HashMap, convert::TryInto, fs, @@ -124,8 +125,14 @@ use crate::{ PreMineSpendStep3OutputsForParties, PreMineSpendStep3OutputsForSelf, PreMineSpendStep4OutputsForLeader, + RecipientInfo, + Step2OutputsForLeader, + Step2OutputsForSelf, + Step3OutputsForParties, + Step3OutputsForSelf, + Step4OutputsForLeader, }, - cli::{CliCommands, MakeItRainTransactionType}, + cli::{CliCommands, CliRecipientInfo, MakeItRainTransactionType}, init::init_wallet, recovery::{get_seed_from_seed_words, wallet_recovery}, utils::db::{get_custom_base_node_peer_from_db, CUSTOM_BASE_NODE_ADDRESS_KEY, CUSTOM_BASE_NODE_PUBLIC_KEY_KEY}, @@ -198,6 +205,7 @@ async fn encumber_aggregate_utxo( metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, + original_maturity: u64, ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), CommandError> { wallet_transaction_service .encumber_aggregate_utxo( @@ -210,6 +218,7 @@ async fn encumber_aggregate_utxo( metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, + original_maturity, ) .await .map_err(CommandError::TransactionServiceError) @@ -811,11 +820,12 @@ pub async fn command_runner( .any(|u| u.commitment() == &pre_mine_outputs[index].commitment); if let Err(e) = file_stream.write_all( format!( - "{},{},{},{},{},{}\n", + "{},{},{},{},{},{},{}\n", index, item.value, item.maturity, - item.maturity + item.fail_safe_height, + item.original_maturity, + item.fail_safe_height, item.beneficiary, if unspent { "unspent" } else { "spent" }, ) @@ -842,32 +852,44 @@ pub async fn command_runner( }, } - let embedded_output = match get_embedded_pre_mine_outputs(vec![args.output_index]) { - Ok(outputs) => outputs[0].clone(), - Err(e) => { - eprintln!("\nError: {}\n", e); - break; - }, - }; - let commitment = embedded_output.commitment.clone(); - let output_hash = embedded_output.hash(); + let args_recipient_info = sort_args_recipient_info(args.recipient_info); + if let Err(e) = verify_no_duplicate_indexes(&args_recipient_info) { + eprintln!("\nError: {} duplicate output indexes detected!\n", e); + break; + } - if args.verify_unspent_outputs { - let unspent_outputs = transaction_service.fetch_unspent_outputs(vec![output_hash]).await?; - if unspent_outputs.is_empty() { - eprintln!( - "\nError: Output with output_hash '{}' has already been spent!\n", - output_hash - ); - break; + let mut recipient_info = Vec::new(); + for item in args_recipient_info { + let embedded_outputs = match get_embedded_pre_mine_outputs(item.output_indexes.clone()) { + Ok(outputs) => outputs, + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; + let output_hashes = embedded_outputs.iter().map(|v| v.hash()).collect::>(); + + if args.verify_unspent_outputs { + let unspent_outputs = transaction_service.fetch_unspent_outputs(output_hashes.clone()).await?; + if unspent_outputs.len() != output_hashes.len() { + let unspent_output_hashes = unspent_outputs.iter().map(|v| v.hash()).collect::>(); + let missing = output_hashes + .iter() + .filter(|&v| !unspent_output_hashes.iter().any(|u| u == v)) + .collect::>(); + eprintln!( + "\nError: Outputs with output_hashes '{:?}' has already been spent!\n", + missing.iter().map(|v| v.to_hex()).collect::>(), + ); + break; + } } - if unspent_outputs[0].commitment() != &commitment { - eprintln!( - "\nError: Mismatched commitment '{}' and output_hash '{}'; not for the same output!\n", - commitment.to_hex(), - output_hash - ); - break; + + for index in item.output_indexes { + recipient_info.push(RecipientInfo { + output_to_be_spend: index, + recipient_address: item.recipient_address.clone(), + }); } } @@ -880,11 +902,8 @@ pub async fn command_runner( }; let session_info = PreMineSpendStep1SessionInfo { session_id: session_id.clone(), - commitment_to_spend: commitment.to_hex(), - output_hash: output_hash.to_hex(), - recipient_address: args.recipient_address, fee_per_gram: args.fee_per_gram, - output_index: args.output_index, + recipient_info, }; let out_file = out_dir.join(get_file_name(SPEND_SESSION_INFO, None)); @@ -957,99 +976,130 @@ pub async fn command_runner( eprintln!("\nError: Alias contains invalid characters! Only alphanumeric and '_' are allowed.\n"); break; } - - let wallet_spend_key = wallet.key_manager_service.get_spend_key().await?; - let script_nonce_key = key_manager_service.get_random_key().await?; - let sender_offset_key = key_manager_service.get_random_key().await?; - let sender_offset_nonce = key_manager_service.get_random_key().await?; - - // Read session info - let session_info = read_session_info::(args.input_file.clone())?; - - if session_info.output_index != args.output_index { - eprintln!( - "\nError: Mismatched output index from leader '{}' vs. '{}'\n", - session_info.output_index, args.output_index - ); + let args_recipient_info = sort_args_recipient_info(args.recipient_info); + if let Err(e) = verify_no_duplicate_indexes(&args_recipient_info) { + eprintln!("\nError: {} duplicate output indexes detected!\n", e); break; } - let embedded_output = get_embedded_pre_mine_outputs(vec![args.output_index])?[0].clone(); - let commitment = embedded_output.commitment.clone(); - let output_hash = embedded_output.hash(); - if session_info.commitment_to_spend != commitment.to_hex() { - eprintln!( - "\nError: Mismatched commitment from leader '{}' vs. '{}'!\n", - session_info.commitment_to_spend, - commitment.to_hex() - ); - break; - } - if session_info.output_hash != output_hash.to_hex() { + // Read session info + let session_info = read_session_info::(args.input_file.clone())?; + // Verify session info + let args_recipient_info_flat = args_recipient_info + .iter() + .flat_map(|v1| { + v1.output_indexes + .iter() + .map(|&v2| RecipientInfo { + output_to_be_spend: v2, + recipient_address: v1.recipient_address.clone(), + }) + .collect::>() + }) + .collect::>(); + if args_recipient_info_flat != session_info.recipient_info { eprintln!( - "\nError: Mismatched output hash from leader '{}' vs. '{}'!\n", - session_info.output_hash, - output_hash.to_hex() + "\nError: Mismatched recipient info! leader {:?} vs. self {:?}\n", + session_info + .recipient_info + .iter() + .map(|v| (v.output_to_be_spend, v.recipient_address.clone())) + .collect::>(), + args_recipient_info_flat + .iter() + .map(|v| (v.output_to_be_spend, v.recipient_address.clone())) + .collect::>() ); break; } - let shared_secret = key_manager_service - .get_diffie_hellman_shared_secret( - &sender_offset_key.key_id, - session_info - .recipient_address - .public_view_key() - .ok_or(CommandError::InvalidArgument("Missing public view key".to_string()))?, - ) - .await?; - let shared_secret_public_key = PublicKey::from_canonical_bytes(shared_secret.as_bytes())?; + let mut outputs_for_leader = Vec::with_capacity(args_recipient_info.len()); + let mut outputs_for_self = Vec::with_capacity(args_recipient_info.len()); + for recipient_info in &args_recipient_info { + let embedded_outputs = match get_embedded_pre_mine_outputs(recipient_info.output_indexes.clone()) { + Ok(outputs) => outputs, + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; + let commitments = embedded_outputs + .iter() + .map(|v| v.commitment.clone()) + .collect::>(); + + for (output_index, commitment) in recipient_info.output_indexes.iter().zip(commitments.iter()) { + let script_nonce_key = key_manager_service.get_random_key().await?; + let sender_offset_key = key_manager_service.get_random_key().await?; + let sender_offset_nonce = key_manager_service.get_random_key().await?; + let shared_secret = key_manager_service + .get_diffie_hellman_shared_secret( + &sender_offset_key.key_id, + recipient_info + .recipient_address + .public_view_key() + .ok_or(CommandError::InvalidArgument("Missing public view key".to_string()))?, + ) + .await?; + let shared_secret_public_key = PublicKey::from_canonical_bytes(shared_secret.as_bytes())?; - let pre_mine_script_key_id = KeyId::Managed { - branch: TransactionKeyManagerBranch::PreMine.get_branch_key(), - index: args.output_index as u64, - }; - let pre_mine_public_script_key = match key_manager_service - .get_public_key_at_key_id(&pre_mine_script_key_id) - .await - { - Ok(key) => key, - Err(e) => { - eprintln!( - "\nError: Could not retrieve script key for output {}: {}\n", - args.output_index, e - ); - break; - }, - }; - let script_input_signature = key_manager_service - .sign_script_message(&pre_mine_script_key_id, commitment.as_bytes()) - .await?; + let pre_mine_script_key_id = KeyId::Managed { + branch: TransactionKeyManagerBranch::PreMine.get_branch_key(), + index: *output_index as u64, + }; + let pre_mine_public_script_key = match key_manager_service + .get_public_key_at_key_id(&pre_mine_script_key_id) + .await + { + Ok(key) => key, + Err(e) => { + eprintln!( + "\nError: Could not retrieve script key for output {}: {}\n", + output_index, e + ); + break; + }, + }; + let script_input_signature = key_manager_service + .sign_script_message(&pre_mine_script_key_id, commitment.as_bytes()) + .await?; + + outputs_for_leader.push(Step2OutputsForLeader { + output_index: *output_index, + recipient_address: recipient_info.recipient_address.clone(), + script_input_signature, + public_script_nonce_key: script_nonce_key.pub_key, + public_sender_offset_key: sender_offset_key.pub_key, + public_sender_offset_nonce_key: sender_offset_nonce.pub_key, + dh_shared_secret_public_key: shared_secret_public_key, + pre_mine_public_script_key, + }); + + outputs_for_self.push(Step2OutputsForSelf { + output_index: *output_index, + recipient_address: recipient_info.recipient_address.clone(), + script_nonce_key_id: script_nonce_key.key_id, + sender_offset_key_id: sender_offset_key.key_id, + sender_offset_nonce_key_id: sender_offset_nonce.key_id, + pre_mine_script_key_id, + }); + } + } let out_dir = out_dir(&session_info.session_id)?; - let step_2_outputs_for_leader = PreMineSpendStep2OutputsForLeader { - script_input_signature, - public_script_nonce_key: script_nonce_key.pub_key, - public_sender_offset_key: sender_offset_key.pub_key, - public_sender_offset_nonce_key: sender_offset_nonce.pub_key, - dh_shared_secret_public_key: shared_secret_public_key, - pre_mine_public_script_key, - }; let out_file_leader = out_dir.join(get_file_name(SPEND_STEP_2_LEADER, Some(args.alias.clone()))); write_json_object_to_file_as_line(&out_file_leader, true, session_info.clone())?; - write_json_object_to_file_as_line(&out_file_leader, false, step_2_outputs_for_leader)?; - - let step_2_outputs_for_self = PreMineSpendStep2OutputsForSelf { + write_json_object_to_file_as_line(&out_file_leader, false, PreMineSpendStep2OutputsForLeader { + outputs_for_leader, alias: args.alias.clone(), - wallet_spend_key_id: wallet_spend_key.key_id, - script_nonce_key_id: script_nonce_key.key_id, - sender_offset_key_id: sender_offset_key.key_id, - sender_offset_nonce_key_id: sender_offset_nonce.key_id, - pre_mine_script_key_id, - }; + })?; + let out_file_self = out_dir.join(get_file_name(SPEND_STEP_2_SELF, None)); write_json_object_to_file_as_line(&out_file_self, true, session_info.clone())?; - write_json_object_to_file_as_line(&out_file_self, false, step_2_outputs_for_self)?; + write_json_object_to_file_as_line(&out_file_self, false, PreMineSpendStep2OutputsForSelf { + outputs_for_self, + alias: args.alias.clone(), + })?; println!(); println!("Concluded step 2 'pre-mine-spend-party-details'"); @@ -1072,88 +1122,180 @@ pub async fn command_runner( // Read session info let session_info = read_verify_session_info::(&args.session_id)?; - - #[allow(clippy::mutable_key_type)] - let mut input_shares = HashMap::new(); - let mut script_signature_public_nonces = Vec::with_capacity(args.input_file_names.len()); - let mut sender_offset_public_key_shares = Vec::with_capacity(args.input_file_names.len()); - let mut metadata_ephemeral_public_key_shares = Vec::with_capacity(args.input_file_names.len()); - let mut dh_shared_secret_shares = Vec::with_capacity(args.input_file_names.len()); + let session_info_indexed = session_info + .recipient_info + .iter() + .map(|v| (v.output_to_be_spend, v.recipient_address.clone())) + .collect::>(); + + // Read and verify party info + let mut party_info = Vec::with_capacity(args.input_file_names.len()); for file_name in args.input_file_names { - // Read party input - let party_info = read_and_verify::( + party_info.push(read_and_verify::( &args.session_id, &file_name, &session_info, - )?; - input_shares.insert(party_info.pre_mine_public_script_key, party_info.script_input_signature); - script_signature_public_nonces.push(party_info.public_script_nonce_key); - sender_offset_public_key_shares.push(party_info.public_sender_offset_key); - metadata_ephemeral_public_key_shares.push(party_info.public_sender_offset_nonce_key); - dh_shared_secret_shares.push(party_info.dh_shared_secret_public_key); + )?); } + for party in &party_info { + let this_party_info = party + .outputs_for_leader + .iter() + .map(|v1| (v1.output_index, v1.recipient_address.clone())) + .collect::>(); - match encumber_aggregate_utxo( - transaction_service.clone(), - session_info.fee_per_gram, - FixedHash::from_hex(&session_info.output_hash) - .map_err(|e| CommandError::InvalidArgument(e.to_string()))?, - Commitment::from_hex(&session_info.commitment_to_spend)?, - input_shares, - script_signature_public_nonces, - sender_offset_public_key_shares, - metadata_ephemeral_public_key_shares, - dh_shared_secret_shares, - session_info.recipient_address.clone(), - ) - .await - { - Ok(( - tx_id, - transaction, - script_pubkey, - total_metadata_ephemeral_public_key, - total_script_nonce, - )) => { - let out_dir = out_dir(&args.session_id)?; - let step_3_outputs_for_self = PreMineSpendStep3OutputsForSelf { tx_id }; - let out_file = out_dir.join(get_file_name(SPEND_STEP_3_SELF, None)); - write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; - write_json_object_to_file_as_line(&out_file, false, step_3_outputs_for_self)?; - - let step_3_outputs_for_parties = PreMineSpendStep3OutputsForParties { - input_stack: transaction.body.inputs()[0].clone().input_data, - input_script: transaction.body.inputs()[0].script().unwrap().clone(), - total_script_key: script_pubkey, - script_signature_ephemeral_commitment: transaction.body.inputs()[0] - .script_signature - .ephemeral_commitment() - .clone(), - script_signature_ephemeral_pubkey: total_script_nonce, - output_commitment: transaction.body.outputs()[0].commitment().clone(), - sender_offset_pubkey: transaction.body.outputs()[0].clone().sender_offset_public_key, - metadata_signature_ephemeral_commitment: transaction.body.outputs()[0] - .metadata_signature - .ephemeral_commitment() - .clone(), - metadata_signature_ephemeral_pubkey: total_metadata_ephemeral_public_key, - encrypted_data: transaction.body.outputs()[0].clone().encrypted_data, - output_features: transaction.body.outputs()[0].clone().features, - }; - let out_file = out_dir.join(get_file_name(SPEND_STEP_3_PARTIES, None)); - write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; - write_json_object_to_file_as_line(&out_file, false, step_3_outputs_for_parties)?; - - println!(); - println!("Concluded step 3 'pre-mine-spend-encumber-aggregate-utxo'"); - println!( - "Send '{}' to parties for step 4", - get_file_name(SPEND_STEP_3_PARTIES, None) + if session_info_indexed != this_party_info { + eprintln!( + "\nError: Mismatched recipient info from '{}', expected {:?} received {:?}!\n", + party.alias, + session_info_indexed + .iter() + .map(|(index, address)| (*index, address.to_hex().clone())) + .collect::>(), + this_party_info + .iter() + .map(|(index, address)| (*index, address.to_hex().clone())) + .collect::>(), ); - println!(); + break; + } + } + + // Flatten and transpose party_info to be indexed by output index + let party_info_flattened = party_info + .iter() + .map(|v1| v1.outputs_for_leader.clone()) + .collect::>(); + let mut party_info_per_index = Vec::with_capacity(party_info_flattened[0].len()); + for i in 0..party_info_flattened[0].len() { + let mut outputs_per_index = Vec::with_capacity(party_info_flattened.len()); + for outputs in &party_info_flattened { + outputs_per_index.push(outputs[i].clone()); + } + party_info_per_index.push(outputs_per_index); + } + + // Encumber outputs + let mut outputs_for_parties = Vec::with_capacity(party_info_per_index.len()); + let mut outputs_for_self = Vec::with_capacity(party_info_per_index.len()); + let pre_mine_items = match get_pre_mine_items(Network::get_current_or_user_setting_or_default()).await { + Ok(items) => items, + Err(e) => { + eprintln!("\nError: {}\n", e); + return Ok(()); }, - Err(e) => eprintln!("\nError: Encumber aggregate transaction error! {}\n", e), + }; + for indexed_info in party_info_per_index { + #[allow(clippy::mutable_key_type)] + let mut input_shares = HashMap::new(); + let mut script_signature_public_nonces = Vec::with_capacity(indexed_info.len()); + let mut sender_offset_public_key_shares = Vec::with_capacity(indexed_info.len()); + let mut metadata_ephemeral_public_key_shares = Vec::with_capacity(indexed_info.len()); + let mut dh_shared_secret_shares = Vec::with_capacity(indexed_info.len()); + let current_index = indexed_info[0].output_index; + let current_recipient_address = indexed_info[0].recipient_address.clone(); + for item in indexed_info { + if current_index != item.output_index { + eprintln!( + "\nError: Mismatched output indexes detected! (expected {}, got {})\n", + current_index, item.output_index + ); + break; + } + if current_recipient_address != item.recipient_address { + eprintln!( + "\nError: Mismatched recipient addresses detected! (expected {}, got {})\n", + current_recipient_address, item.recipient_address + ); + break; + } + input_shares.insert(item.pre_mine_public_script_key, item.script_input_signature); + script_signature_public_nonces.push(item.public_script_nonce_key); + sender_offset_public_key_shares.push(item.public_sender_offset_key); + metadata_ephemeral_public_key_shares.push(item.public_sender_offset_nonce_key); + dh_shared_secret_shares.push(item.dh_shared_secret_public_key); + } + + let original_maturity = pre_mine_items[current_index].original_maturity; + let embedded_output = match get_embedded_pre_mine_outputs(vec![current_index]) { + Ok(outputs) => outputs[0].clone(), + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; + + match encumber_aggregate_utxo( + transaction_service.clone(), + session_info.fee_per_gram, + embedded_output.hash(), + embedded_output.commitment.clone(), + input_shares, + script_signature_public_nonces, + sender_offset_public_key_shares, + metadata_ephemeral_public_key_shares, + dh_shared_secret_shares, + current_recipient_address, + original_maturity, + ) + .await + { + Ok(( + tx_id, + transaction, + script_pubkey, + total_metadata_ephemeral_public_key, + total_script_nonce, + )) => { + outputs_for_parties.push(Step3OutputsForParties { + output_index: current_index, + input_stack: transaction.body.inputs()[0].clone().input_data, + input_script: transaction.body.inputs()[0].script().unwrap().clone(), + total_script_key: script_pubkey, + script_signature_ephemeral_commitment: transaction.body.inputs()[0] + .script_signature + .ephemeral_commitment() + .clone(), + script_signature_ephemeral_pubkey: total_script_nonce, + output_commitment: transaction.body.outputs()[0].commitment().clone(), + sender_offset_pubkey: transaction.body.outputs()[0].clone().sender_offset_public_key, + metadata_signature_ephemeral_commitment: transaction.body.outputs()[0] + .metadata_signature + .ephemeral_commitment() + .clone(), + metadata_signature_ephemeral_pubkey: total_metadata_ephemeral_public_key, + encrypted_data: transaction.body.outputs()[0].clone().encrypted_data, + output_features: transaction.body.outputs()[0].clone().features, + }); + outputs_for_self.push(Step3OutputsForSelf { + output_index: current_index, + tx_id, + }); + }, + Err(e) => eprintln!("\nError: Encumber aggregate transaction error! {}\n", e), + } } + + let out_dir = out_dir(&args.session_id)?; + let out_file = out_dir.join(get_file_name(SPEND_STEP_3_SELF, None)); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, PreMineSpendStep3OutputsForSelf { + outputs_for_self, + })?; + + let out_file = out_dir.join(get_file_name(SPEND_STEP_3_PARTIES, None)); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, PreMineSpendStep3OutputsForParties { + outputs_for_parties, + })?; + + println!(); + println!("Concluded step 3 'pre-mine-spend-encumber-aggregate-utxo'"); + println!( + "Send '{}' to parties for step 4", + get_file_name(SPEND_STEP_3_PARTIES, None) + ); + println!(); }, PreMineSpendInputOutputSigs(args) => { match *key_manager_service.get_wallet_type().await { @@ -1167,105 +1309,154 @@ pub async fn command_runner( // Read session info let session_info = read_verify_session_info::(&args.session_id)?; // Read leader input - let leader_info = read_and_verify::( + let leader_info_indexed = read_and_verify::( &args.session_id, &get_file_name(SPEND_STEP_3_PARTIES, None), &session_info, )?; // Read own party info - let party_info = read_and_verify::( + let party_info_indexed = read_and_verify::( &args.session_id, &get_file_name(SPEND_STEP_2_SELF, None), &session_info, )?; - // Script signature - let challenge = TransactionInput::build_script_signature_challenge( - &TransactionInputVersion::get_current_version(), - &leader_info.script_signature_ephemeral_commitment, - &leader_info.script_signature_ephemeral_pubkey, - &leader_info.input_script, - &leader_info.input_stack, - &leader_info.total_script_key, - &Commitment::from_hex(&session_info.commitment_to_spend)?, - ); + // Verify index consistency + let session_info_indexes = session_info + .recipient_info + .iter() + .map(|v| v.output_to_be_spend) + .collect::>(); + let leader_info_indexes = leader_info_indexed + .outputs_for_parties + .iter() + .map(|v| v.output_index) + .collect::>(); + let party_info_indexes = party_info_indexed + .outputs_for_self + .iter() + .map(|v| v.output_index) + .collect::>(); + if session_info_indexes != leader_info_indexes || session_info_indexes != party_info_indexes { + eprintln!( + "\nError: Mismatched output indexes detected! session {:?} vs. leader {:?} vs. self {:?}\n", + session_info_indexes, leader_info_indexes, party_info_indexes + ); + break; + } - let script_signature = match key_manager_service - .sign_with_nonce_and_challenge( - &party_info.pre_mine_script_key_id, - &party_info.script_nonce_key_id, - &challenge, - ) - .await + let mut outputs_for_leader = Vec::with_capacity(party_info_indexed.outputs_for_self.len()); + for (leader_info, party_info) in leader_info_indexed + .outputs_for_parties + .iter() + .zip(party_info_indexed.outputs_for_self.iter()) { - Ok(signature) => signature, - Err(e) => { - eprintln!("\nError: Script signature SignMessage error! {}\n", e); - break; - }, - }; + let embedded_output = match get_embedded_pre_mine_outputs(vec![party_info.output_index]) { + Ok(outputs) => outputs[0].clone(), + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; - // Metadata signature - let script_offset = key_manager_service - .get_script_offset(&vec![party_info.pre_mine_script_key_id], &vec![party_info - .sender_offset_key_id - .clone()]) - .await?; - let challenge = TransactionOutput::build_metadata_signature_challenge( - &TransactionOutputVersion::get_current_version(), - &script!(PushPubKey(Box::new( - session_info.recipient_address.public_spend_key().clone() - )))?, - &leader_info.output_features, - &leader_info.sender_offset_pubkey, - &leader_info.metadata_signature_ephemeral_commitment, - &leader_info.metadata_signature_ephemeral_pubkey, - &leader_info.output_commitment, - &Covenant::default(), - &leader_info.encrypted_data, - MicroMinotari::zero(), - ); + // Script signature + let challenge = TransactionInput::build_script_signature_challenge( + &TransactionInputVersion::get_current_version(), + &leader_info.script_signature_ephemeral_commitment, + &leader_info.script_signature_ephemeral_pubkey, + &leader_info.input_script, + &leader_info.input_stack, + &leader_info.total_script_key, + &embedded_output.commitment, + ); - let metadata_signature = match key_manager_service - .sign_with_nonce_and_challenge( - &party_info.sender_offset_key_id, - &party_info.sender_offset_nonce_key_id, - &challenge, - ) - .await - { - Ok(signature) => signature, - Err(e) => { - eprintln!("\nError: Metadata signature SignMessage error! {}\n", e); + let script_signature = match key_manager_service + .sign_with_nonce_and_challenge( + &party_info.pre_mine_script_key_id, + &party_info.script_nonce_key_id, + &challenge, + ) + .await + { + Ok(signature) => signature, + Err(e) => { + eprintln!("\nError: Script signature SignMessage error! {}\n", e); + break; + }, + }; + + // Metadata signature + let script_offset = key_manager_service + .get_script_offset(&vec![party_info.pre_mine_script_key_id.clone()], &vec![party_info + .sender_offset_key_id + .clone()]) + .await?; + let challenge = TransactionOutput::build_metadata_signature_challenge( + &TransactionOutputVersion::get_current_version(), + &script!(PushPubKey(Box::new( + party_info.recipient_address.public_spend_key().clone() + )))?, + &leader_info.output_features, + &leader_info.sender_offset_pubkey, + &leader_info.metadata_signature_ephemeral_commitment, + &leader_info.metadata_signature_ephemeral_pubkey, + &leader_info.output_commitment, + &Covenant::default(), + &leader_info.encrypted_data, + MicroMinotari::zero(), + ); + + let metadata_signature = match key_manager_service + .sign_with_nonce_and_challenge( + &party_info.sender_offset_key_id, + &party_info.sender_offset_nonce_key_id, + &challenge, + ) + .await + { + Ok(signature) => signature, + Err(e) => { + eprintln!("\nError: Metadata signature SignMessage error! {}\n", e); + break; + }, + }; + + if script_signature.get_signature() == Signature::default().get_signature() || + metadata_signature.get_signature() == Signature::default().get_signature() + { + eprintln!( + "\nError: Script and/or metadata signatures not created (index {})!\n", + party_info.output_index + ); break; - }, - }; + } - if script_signature.get_signature() == Signature::default().get_signature() || - metadata_signature.get_signature() == Signature::default().get_signature() - { - eprintln!("\nError: Script and/or metadata signatures not created!\n"); - break; - } else { - let step_4_outputs_for_leader = PreMineSpendStep4OutputsForLeader { + outputs_for_leader.push(Step4OutputsForLeader { + output_index: party_info.output_index, script_signature, metadata_signature, script_offset, - }; + }); + } - let out_dir = out_dir(&args.session_id)?; - let out_file = out_dir.join(get_file_name(SPEND_STEP_4_LEADER, Some(party_info.alias.clone()))); - write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; - write_json_object_to_file_as_line(&out_file, false, step_4_outputs_for_leader)?; + let out_dir = out_dir(&args.session_id)?; + let out_file = out_dir.join(get_file_name( + SPEND_STEP_4_LEADER, + Some(party_info_indexed.alias.clone()), + )); + write_json_object_to_file_as_line(&out_file, true, session_info.clone())?; + write_json_object_to_file_as_line(&out_file, false, PreMineSpendStep4OutputsForLeader { + outputs_for_leader, + alias: party_info_indexed.alias.clone(), + })?; - println!(); - println!("Concluded step 4 'pre-mine-spend-input-output-sigs'"); - println!( - "Send '{}' to leader for step 5", - get_file_name(SPEND_STEP_4_LEADER, Some(party_info.alias)) - ); - println!(); - } + println!(); + println!("Concluded step 4 'pre-mine-spend-input-output-sigs'"); + println!( + "Send '{}' to leader for step 5", + get_file_name(SPEND_STEP_4_LEADER, Some(party_info_indexed.alias)) + ); + println!(); }, PreMineSpendAggregateTransaction(args) => { match *key_manager_service.get_wallet_type().await { @@ -1279,21 +1470,15 @@ pub async fn command_runner( // Read session info let session_info = read_verify_session_info::(&args.session_id)?; - let mut metadata_signatures = Vec::with_capacity(args.input_file_names.len()); - let mut script_signatures = Vec::with_capacity(args.input_file_names.len()); - let mut offset = PrivateKey::default(); + // Read other parties info + let mut party_info = Vec::with_capacity(args.input_file_names.len()); for file_name in args.input_file_names { - // Read party input - let party_info = read_and_verify::( + party_info.push(read_and_verify::( &args.session_id, &file_name, &session_info, - )?; - metadata_signatures.push(party_info.metadata_signature); - script_signatures.push(party_info.script_signature); - offset = &offset + &party_info.script_offset; + )?); } - // Read own party info let leader_info = read_and_verify::( &args.session_id, @@ -1301,22 +1486,170 @@ pub async fn command_runner( &session_info, )?; - match finalise_aggregate_utxo( - transaction_service.clone(), - leader_info.tx_id.as_u64(), - metadata_signatures, - script_signatures, - offset, - ) - .await + // Verify index consistency + let session_info_indexes = session_info + .recipient_info + .iter() + .map(|v| v.output_to_be_spend) + .collect::>(); + let leader_info_indexes = leader_info + .outputs_for_self + .iter() + .map(|v| v.output_index) + .collect::>(); + if session_info_indexes != leader_info_indexes { + eprintln!( + "\nError: Mismatched output indexes detected! session {:?} vs. leader (self) {:?}\n", + session_info_indexes, leader_info_indexes + ); + break; + } + for party in &party_info { + let party_info_indexes = party + .outputs_for_leader + .iter() + .map(|v| v.output_index) + .collect::>(); + if session_info_indexes != party_info_indexes { + eprintln!( + "\nError: Mismatched output indexes from '{}' detected! session {:?} vs. party {:?}\n", + party.alias, session_info_indexes, party_info_indexes + ); + break; + } + } + + // Flatten and transpose party_info to be indexed by output index + let party_info_flattened = party_info + .iter() + .map(|v1| v1.outputs_for_leader.clone()) + .collect::>(); + let mut party_info_per_index = Vec::with_capacity(party_info_flattened[0].len()); + let number_of_parties = party_info_flattened.len(); + for i in 0..party_info_flattened[0].len() { + let mut outputs_per_index = Vec::with_capacity(number_of_parties); + for outputs in &party_info_flattened { + outputs_per_index.push(outputs[i].clone()); + } + party_info_per_index.push(outputs_per_index); + } + + // Create finalized spend transactions + let mut outputs = Vec::new(); + let mut kernels = Vec::new(); + for (indexed_info, leader_self) in party_info_per_index.iter().zip(leader_info.outputs_for_self.iter()) { - Ok(_v) => { - println!(); - println!("Concluded step 5 'pre-mine-spend-aggregate-transaction'"); - println!(); - }, - Err(e) => println!("\nError: Error completing transaction! {}\n", e), + let mut metadata_signatures = Vec::with_capacity(party_info_per_index.len()); + let mut script_signatures = Vec::with_capacity(party_info_per_index.len()); + let mut offset = PrivateKey::default(); + for party_info in indexed_info { + metadata_signatures.push(party_info.metadata_signature.clone()); + script_signatures.push(party_info.script_signature.clone()); + offset = &offset + &party_info.script_offset; + } + + if let Err(e) = finalise_aggregate_utxo( + transaction_service.clone(), + leader_self.tx_id.as_u64(), + metadata_signatures, + script_signatures, + offset, + ) + .await + { + eprintln!( + "\nError: Error completing transaction '{}'! ({})\n", + leader_self.tx_id, e + ); + break; + } + + if args.print_to_console || args.save_to_file { + match transaction_service.get_any_transaction(leader_self.tx_id).await { + Ok(Some(WalletTransaction::Completed(tx))) => { + if args.save_to_file { + for output in tx.transaction.body.outputs() { + outputs.push(output.clone()); + } + for kernel in tx.transaction.body.kernels() { + kernels.push(kernel.clone()); + } + } + if args.print_to_console { + let tx_console = serde_json::to_string(&tx.transaction).unwrap_or_else(|_| { + format!("Transaction to json conversion error! ('{}')", leader_self.tx_id) + }); + println!("Tx_Id: {}, Tx: {}", leader_self.tx_id, tx_console); + } + }, + Ok(_) => { + eprintln!( + "\nError: Transaction '{}' is not in a completed state!\n", + leader_self.tx_id + ); + break; + }, + Err(e) => { + eprintln!("\nError: Transaction '{}' not found! ({})\n", leader_self.tx_id, e); + break; + }, + } + } + } + + if args.save_to_file { + let file_name = get_pre_mine_file_name(); + let out_dir_path = out_dir(&args.session_id)?; + let out_file = out_dir_path.join(&file_name); + let mut file_stream = match File::create(&out_file) { + Ok(file) => file, + Err(e) => { + eprintln!("\nError: Could not create the pre-mine file ({})\n", e); + break; + }, + }; + + let mut error = false; + for output in outputs { + let utxo_s = match serde_json::to_string(&output) { + Ok(val) => val, + Err(e) => { + eprintln!("\nError: Could not serialize UTXO ({})\n", e); + error = true; + break; + }, + }; + if let Err(e) = file_stream.write_all(format!("{}\n", utxo_s).as_bytes()) { + eprintln!("\nError: Could not write UTXO to file ({})\n", e); + error = true; + break; + } + } + if error { + break; + } + for kernel in kernels { + let kernel_s = match serde_json::to_string(&kernel) { + Ok(val) => val, + Err(e) => { + eprintln!("\nError: Could not serialize kernel ({})\n", e); + break; + }, + }; + if let Err(e) = file_stream.write_all(format!("{}\n", kernel_s).as_bytes()) { + eprintln!("\nError: Could not write the genesis file ({})\n", e); + error = true; + break; + } + } + if error { + break; + } } + + println!(); + println!("Concluded step 5 'pre-mine-spend-aggregate-transaction'"); + println!(); }, SendMinotari(args) => { match send_tari( @@ -2019,6 +2352,42 @@ pub async fn command_runner( Ok(()) } +fn get_pre_mine_file_name() -> String { + match Network::get_current_or_user_setting_or_default() { + Network::MainNet => "mainnet_pre_mine_addition.json".to_string(), + Network::StageNet => "stagenet_pre_mine_addition.json".to_string(), + Network::NextNet => "nextnet_pre_mine_addition.json".to_string(), + Network::LocalNet => "esmeralda_pre_mine_addition.json".to_string(), + Network::Igor => "igor_pre_mine_addition.json".to_string(), + Network::Esmeralda => "esmeralda_pre_mine_addition.json".to_string(), + } +} + +fn verify_no_duplicate_indexes(recipient_info: &[CliRecipientInfo]) -> Result<(), String> { + let mut all_indexes = recipient_info + .iter() + .flat_map(|v| v.output_indexes.clone()) + .collect::>(); + all_indexes.sort(); + let all_indexes_len = all_indexes.len(); + all_indexes.dedup(); + if all_indexes_len == all_indexes.len() { + Ok(()) + } else { + Err(format!( + "{}", + max(all_indexes_len, all_indexes.len()) - min(all_indexes_len, all_indexes.len()) + )) + } +} + +fn sort_args_recipient_info(recipient_info: Vec) -> Vec { + let mut args_recipient_info = recipient_info; + args_recipient_info.sort_by(|a, b| a.recipient_address.to_hex().cmp(&b.recipient_address.to_hex())); + args_recipient_info.iter_mut().for_each(|v| v.output_indexes.sort()); + args_recipient_info +} + fn get_embedded_pre_mine_outputs(output_indexes: Vec) -> Result, CommandError> { let utxos = get_all_embedded_pre_mine_outputs()?; diff --git a/applications/minotari_console_wallet/src/automation/mod.rs b/applications/minotari_console_wallet/src/automation/mod.rs index 771268fdf9..bba470a7be 100644 --- a/applications/minotari_console_wallet/src/automation/mod.rs +++ b/applications/minotari_console_wallet/src/automation/mod.rs @@ -44,10 +44,13 @@ use tari_script::{CheckSigSchnorrSignature, ExecutionStack, TariScript}; struct PreMineSpendStep1SessionInfo { session_id: String, fee_per_gram: MicroMinotari, - commitment_to_spend: String, - output_hash: String, + recipient_info: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct RecipientInfo { + output_to_be_spend: usize, recipient_address: TariAddress, - output_index: usize, } impl SessionId for PreMineSpendStep1SessionInfo { @@ -59,8 +62,14 @@ impl SessionId for PreMineSpendStep1SessionInfo { // Step 2 outputs for self with `PreMineSpendPartyDetails` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct PreMineSpendStep2OutputsForSelf { + outputs_for_self: Vec, alias: String, - wallet_spend_key_id: TariKeyId, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct Step2OutputsForSelf { + output_index: usize, + recipient_address: TariAddress, script_nonce_key_id: TariKeyId, sender_offset_key_id: TariKeyId, sender_offset_nonce_key_id: TariKeyId, @@ -70,6 +79,14 @@ struct PreMineSpendStep2OutputsForSelf { // Step 2 outputs for leader with `PreMineSpendPartyDetails` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct PreMineSpendStep2OutputsForLeader { + outputs_for_leader: Vec, + alias: String, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +pub struct Step2OutputsForLeader { + output_index: usize, + recipient_address: TariAddress, script_input_signature: CheckSigSchnorrSignature, public_script_nonce_key: PublicKey, public_sender_offset_key: PublicKey, @@ -81,12 +98,24 @@ struct PreMineSpendStep2OutputsForLeader { // Step 3 outputs for self with `PreMineSpendEncumberAggregateUtxo` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct PreMineSpendStep3OutputsForSelf { + outputs_for_self: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step3OutputsForSelf { + output_index: usize, tx_id: TxId, } // Step 3 outputs for parties with `PreMineSpendEncumberAggregateUtxo` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct PreMineSpendStep3OutputsForParties { + outputs_for_parties: Vec, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step3OutputsForParties { + output_index: usize, input_stack: ExecutionStack, input_script: TariScript, total_script_key: PublicKey, @@ -103,6 +132,13 @@ struct PreMineSpendStep3OutputsForParties { // Step 4 outputs for leader with `PreMineSpendInputOutputSigs` #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] struct PreMineSpendStep4OutputsForLeader { + outputs_for_leader: Vec, + alias: String, +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] +struct Step4OutputsForLeader { + output_index: usize, script_signature: Signature, metadata_signature: Signature, script_offset: PrivateKey, diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index 2d4ee2adbb..ae0baddbc9 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -23,6 +23,7 @@ use std::{ fmt::{Debug, Display, Formatter}, path::PathBuf, + str::FromStr, time::Duration, }; @@ -190,19 +191,64 @@ pub struct PreMineSpendSessionInfoArgs { #[clap(long)] pub fee_per_gram: MicroMinotari, #[clap(long)] - pub output_index: usize, - #[clap(long)] - pub recipient_address: TariAddress, + pub recipient_info: Vec, #[clap(long)] pub verify_unspent_outputs: bool, } +#[derive(Debug, Args, Clone)] +pub struct CliRecipientInfo { + pub output_indexes: Vec, + pub recipient_address: TariAddress, +} + +impl FromStr for CliRecipientInfo { + type Err = String; + + fn from_str(s: &str) -> Result { + // Parse 'RecipientInfo' from "[,,]:" + if !s.contains(':') { + return Err("Invalid 'recipient-info', could not find address separator ':'".to_string()); + } + let parts: Vec<&str> = s.split(':').collect(); + if parts.len() != 2 { + return Err(format!( + "Invalid 'recipient-info', needs exactly 2 parts, found {}", + parts.len() + )); + } + + // Parse output indexes + if !parts[0].starts_with('[') && !parts[0].ends_with(']') { + return Err("Invalid 'recipient-info' part 1; array bounds must be indicated with '[' and ']'".to_string()); + } + let binding = parts[0].replace("[", "").replace("]", ""); + let parts_0 = binding.split(',').collect::>(); + let output_indexes = parts_0 + .iter() + .map(|v| { + v.parse() + .map_err(|e| format!("'recipient_info' - invalid output_index: {}", e)) + }) + .collect::, String>>()?; + + // Parse recipient address + let recipient_address = TariAddress::from_base58(parts[1]) + .map_err(|e| format!("'recipient_info' - invalid recipient address: {}", e))?; + + Ok(CliRecipientInfo { + output_indexes, + recipient_address, + }) + } +} + #[derive(Debug, Args, Clone)] pub struct PreMineSpendPartyDetailsArgs { #[clap(long)] pub input_file: PathBuf, #[clap(long)] - pub output_index: usize, + pub recipient_info: Vec, #[clap(long)] pub alias: String, } @@ -227,6 +273,10 @@ pub struct PreMineSpendAggregateTransactionArgs { pub session_id: String, #[clap(long)] pub input_file_names: Vec, + #[clap(long)] + pub save_to_file: bool, + #[clap(long)] + pub print_to_console: bool, } #[derive(Debug, Args, Clone)] diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index c28da1add4..5ff229b273 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -543,11 +543,14 @@ mod test { pre-mine-spend-get-output-status - pre-mine-spend-session-info --fee-per-gram 2 --output-index 123 --recipient-address \ + pre-mine-spend-session-info --fee-per-gram 2 \ + --recipient-info=[1,123,313]:\ f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA \ --verify-unspent-outputs - pre-mine-spend-party-details --input-file ./step_1_session_info.txt --output-index 123 --alias alice + pre-mine-spend-party-details --input-file ./step_1_session_info.txt --alias alice \ + --recipient-info=[1,123,313]:\ + f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA pre-mine-spend-encumber-aggregate-utxo --session-id ee1643655c \ --input-file-names=step_2_for_leader_from_alice.txt --input-file-names=step_2_for_leader_from_bob.txt \ diff --git a/base_layer/core/src/blocks/pre_mine/mod.rs b/base_layer/core/src/blocks/pre_mine/mod.rs index 038300541a..a3e890a287 100644 --- a/base_layer/core/src/blocks/pre_mine/mod.rs +++ b/base_layer/core/src/blocks/pre_mine/mod.rs @@ -44,7 +44,7 @@ use crate::{ SecretTransactionKeyManagerInterface, TransactionKeyManagerInterface, }, - tari_amount::MicroMinotari, + tari_amount::{MicroMinotari, Minotari}, transaction_components::{ encrypted_data::PaymentId, CoinBaseExtra, @@ -88,7 +88,7 @@ pub struct Apportionment { /// Percentage of total tokens pub percentage: u64, /// Total tokens for this apportionment - pub tokens_amount: u64, + pub tokens_amount: Minotari, /// Token release cadence schedule pub schedule: Option, } @@ -101,23 +101,59 @@ pub struct ReleaseCadence { /// Monthly fraction release factor pub monthly_fraction_denominator: u64, /// Upfront release percentage - pub upfront_release: Option, + pub upfront_release: Vec, /// Expected payout period in blocks from after the initial lockup pub expected_payout_period_blocks: u64, } -/// The upfront percentage of the total tokens to be released +/// The upfront release of tokens +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ReleaseStrategy { + /// Proportional upfront release + Proportional(ProportionalRelease), + /// Custom specified upfront release + Custom(Vec), + /// Upfront release taken from the regular cadence + FromCadence(Vec), +} + +impl Default for ReleaseStrategy { + fn default() -> Self { + Self::Proportional(ProportionalRelease::default()) + } +} + +/// The upfront tokens to be released on a proportional basis #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct UpfrontRelease { +pub struct ProportionalRelease { /// The fraction of the total tokens to be released upfront pub percentage: u64, /// The number of tokens it has to be divided into pub number_of_tokens: u64, - /// This is a tuple of custom valued upfront tokens (Tari value, block_height) - pub custom_upfront_tokens: Vec<(u64, u64)>, } -fn get_expected_payout_period_blocks(network: Network) -> u64 { +/// The upfront tokens to be released on a custom schedule +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CustomRelease { + /// The value of the token + pub value: Minotari, + /// The maturity of the token + pub maturity: u64, +} + +/// The upfront tokens to be released on a cadence basis, where the token value is removed from a specific period in the +/// parent schedule +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct CadenceRelease { + /// The value of the token + pub value: Minotari, + /// The period in the release cadence the token is taken from + pub taken_from_period: u64, + /// The maturity of the token + pub maturity: u64, +} + +fn get_expected_payout_grace_period_blocks(network: Network) -> u64 { match network { Network::MainNet => { BLOCKS_PER_DAY * 30 * 6 // 6 months @@ -134,67 +170,473 @@ pub fn get_tokenomics_pre_mine_unlock_schedule(network: Network) -> UnlockSchedu network_rewards: Apportionment { beneficiary: "network_rewards".to_string(), percentage: 70, - tokens_amount: 14_700_000_000, + tokens_amount: 14_700_000_000.into(), schedule: None, }, protocol: Apportionment { beneficiary: "protocol".to_string(), percentage: 9, - tokens_amount: 1_890_000_000, + tokens_amount: 1_890_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 180, monthly_fraction_denominator: 48, - upfront_release: Some(UpfrontRelease { - percentage: 40, - number_of_tokens: 20, - // 129,600 = 720 (blocks per day) * 30 (days per month) * 6 (months) - custom_upfront_tokens: vec![(1, 0), (1, 0), (1, 129_600), (1, 129_600)], - }), - expected_payout_period_blocks: get_expected_payout_period_blocks(network), + upfront_release: vec![ + ReleaseStrategy::Proportional(ProportionalRelease { + percentage: 40, + number_of_tokens: 20, + }), + ReleaseStrategy::Custom({ + // 129,600 = 720 (blocks per day) * 30 (days per month) * 6 (months) + vec![ + CustomRelease { + value: 1.into(), + maturity: 0, + }, + CustomRelease { + value: 1.into(), + maturity: 0, + }, + CustomRelease { + value: 1.into(), + maturity: 129_600, + }, + CustomRelease { + value: 1.into(), + maturity: 129_600, + }, + ] + }), + ], + expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network), }), }, community: Apportionment { beneficiary: "community".to_string(), percentage: 5, - tokens_amount: 1_050_000_000, + tokens_amount: 1_050_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 180, monthly_fraction_denominator: 12, - upfront_release: None, - expected_payout_period_blocks: get_expected_payout_period_blocks(network), + upfront_release: vec![], + expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network), }), }, contributors: Apportionment { beneficiary: "contributors".to_string(), percentage: 4, - tokens_amount: 840_000_000, + tokens_amount: 840_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 365, monthly_fraction_denominator: 60, - upfront_release: None, - expected_payout_period_blocks: get_expected_payout_period_blocks(network), + upfront_release: contributors_upfront_release(), + expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network), }), }, participants: Apportionment { beneficiary: "participants".to_string(), percentage: 12, - tokens_amount: 2_520_000_000, + tokens_amount: 2_520_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 365, monthly_fraction_denominator: 24, - upfront_release: None, - expected_payout_period_blocks: get_expected_payout_period_blocks(network), + upfront_release: vec![], + expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network), }), }, } } +#[allow(clippy::too_many_lines)] +fn contributors_upfront_release() -> Vec { + vec![ + ReleaseStrategy::FromCadence({ + vec![ + CadenceRelease { + value: 809_645.into(), + taken_from_period: 0, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 1, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 2, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 3, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 4, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 5, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 6, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 7, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 8, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 9, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 10, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 11, + maturity: 0, + }, + CadenceRelease { + value: 809_645.into(), + taken_from_period: 12, + maturity: 0, + }, + ] + }), + ReleaseStrategy::FromCadence({ + vec![ + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 0, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 1, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 2, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 3, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 4, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 5, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 6, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 7, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 8, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 9, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 10, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 11, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 12, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 13, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 14, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 15, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 16, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 17, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 18, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 19, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 20, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 21, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 22, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 23, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 24, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 25, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 26, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 27, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 28, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 29, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 30, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 31, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 32, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 33, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 34, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 35, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 36, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 37, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 38, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 39, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 40, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 41, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 42, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 43, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 44, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 45, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 46, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 47, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 48, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 49, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 50, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 51, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 52, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 53, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 54, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 55, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 56, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 57, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 58, + maturity: 0, + }, + CadenceRelease { + value: 6_300_000.into(), + taken_from_period: 59, + maturity: 0, + }, + ] + }), + ] +} + /// Pre-mine values #[derive(Debug)] pub struct PreMineItem { + /// The value of the pre-mine pub value: MicroMinotari, + /// The maturity of the pre-mine at which it can be spend pub maturity: u64, + /// The original maturity of the pre-mine taken into account any pre-release strategy + pub original_maturity: u64, + /// The fail-safe height (absolute height) at which the pre-mine can be spent by a backup or fail-safe wallet pub fail_safe_height: u64, + /// The beneficiary of the pre-mine pub beneficiary: String, } @@ -205,8 +647,14 @@ pub fn get_pre_mine_value(network: Network) -> Result { Ok(pre_mine_items.iter().map(|item| item.value).sum::()) } +struct CadenceItem { + taken_from_period: u64, + value: Minotari, +} + /// Create a list of (token value, maturity in blocks) according to the amounts in the unlock schedule, based on the /// apportionment and release cadence where 1 day equals 24 * 60 / 2 blocks. +#[allow(clippy::too_many_lines)] pub fn create_pre_mine_output_values(schedule: UnlockSchedule) -> Result, String> { let mut values_with_maturity = Vec::new(); let days_per_month = 365.25 / 12f64; @@ -220,70 +668,161 @@ pub fn create_pre_mine_output_values(schedule: UnlockSchedule) -> Result 100 { - return Err(format!( - "Upfront percentage must be less than or equal to 100 in {:?}", - apportionment - )); - } - if apportionment - .tokens_amount - .checked_mul(1_000_000 * upfront_release.percentage) - .is_none() - { - return Err(format!("Minotari calculation overflow in {:?}", apportionment)); - } - let mut tokens_value = apportionment.tokens_amount * 1_000_000; - if upfront_release.percentage > 0 { - let upfront_tokens = tokens_value * upfront_release.percentage / 100; - tokens_value -= upfront_tokens; - let value_per_round = upfront_tokens / upfront_release.number_of_tokens; - let mut assigned_tokens = 0; - for _ in 0..upfront_release.number_of_tokens - 1 { - values_with_maturity.push(PreMineItem { - value: MicroMinotari::from(value_per_round), - maturity: 0, - fail_safe_height: schedule.expected_payout_period_blocks, - beneficiary: apportionment.beneficiary.clone(), - }); - assigned_tokens += value_per_round; + let mut tokens_value = apportionment.tokens_amount.uT().as_u64(); + let mut early_payout = Vec::new(); + + // Upfront release + for item in &schedule.upfront_release { + match item { + ReleaseStrategy::Proportional(upfront_release) => { + if upfront_release.percentage > 100 { + return Err(format!( + "Upfront percentage must be less than or equal to 100 in {:?}", + apportionment + )); + } + if apportionment + .tokens_amount + .uT() + .as_u64() + .checked_mul(upfront_release.percentage) + .is_none() + { + return Err(format!("Minotari calculation overflow in {:?}", apportionment)); + } + if upfront_release.percentage > 0 { + let upfront_tokens = tokens_value * upfront_release.percentage / 100; + tokens_value -= upfront_tokens; + let value_per_round = upfront_tokens / upfront_release.number_of_tokens; + let mut assigned_tokens = 0; + for _ in 0..upfront_release.number_of_tokens - 1 { + values_with_maturity.push(PreMineItem { + value: MicroMinotari::from(value_per_round), + maturity: 0, + original_maturity: 0, + fail_safe_height: schedule.expected_payout_period_blocks, + beneficiary: apportionment.beneficiary.clone(), + }); + assigned_tokens += value_per_round; + } + values_with_maturity.push(PreMineItem { + value: MicroMinotari::from(upfront_tokens - assigned_tokens), + maturity: 0, + original_maturity: 0, + fail_safe_height: schedule.expected_payout_period_blocks, + beneficiary: apportionment.beneficiary.clone(), + }); + } + }, + ReleaseStrategy::Custom(upfront_release) => { + for release in upfront_release { + tokens_value -= release.value.uT().as_u64(); + values_with_maturity.push(PreMineItem { + value: release.value.uT(), + maturity: release.maturity, + original_maturity: release.maturity, + fail_safe_height: release.maturity + schedule.expected_payout_period_blocks, + beneficiary: apportionment.beneficiary.clone(), + }); + } + }, + ReleaseStrategy::FromCadence(upfront_release) => { + for release in upfront_release { + early_payout.push(CadenceItem { + taken_from_period: release.taken_from_period, + value: release.value, + }); + let original_maturity = schedule.initial_lockup_days * BLOCKS_PER_DAY + + release.taken_from_period * blocks_per_month; + values_with_maturity.push(PreMineItem { + value: release.value.uT(), + maturity: release.maturity, + original_maturity, + fail_safe_height: original_maturity + schedule.expected_payout_period_blocks, + beneficiary: apportionment.beneficiary.clone(), + }); + } + }, } - values_with_maturity.push(PreMineItem { - value: MicroMinotari::from(upfront_tokens - assigned_tokens), - maturity: 0, - fail_safe_height: schedule.expected_payout_period_blocks, - beneficiary: apportionment.beneficiary.clone(), - }); } - for (value, maturity) in upfront_release.custom_upfront_tokens { - let utxo_value = value * 1_000_000; - tokens_value -= utxo_value; - values_with_maturity.push(PreMineItem { - value: MicroMinotari::from(utxo_value), - maturity, - fail_safe_height: schedule.expected_payout_period_blocks, - beneficiary: apportionment.beneficiary.clone(), + + // Combine all upfront 'ReleaseStrategy::FromCadence' payouts into a single value per period + early_payout.sort_by_key(|x| x.taken_from_period); + let mut periods = early_payout + .iter() + .map(|item| item.taken_from_period) + .collect::>(); + periods.dedup(); + let mut early_payouts_summed = Vec::with_capacity(periods.len()); + for period in periods { + let period_value: Minotari = MicroMinotari::from( + early_payout + .iter() + .filter(|item| item.taken_from_period == period) + .map(|item| item.value.uT().as_u64()) + .sum::(), + ) + .into(); + early_payouts_summed.push(CadenceItem { + taken_from_period: period, + value: period_value, }); } + + // Monthly release let monthly_tokens = tokens_value / schedule.monthly_fraction_denominator; let mut total_tokens = 0; let mut maturity = 0; for i in 0..schedule.monthly_fraction_denominator - 1 { total_tokens += monthly_tokens; maturity = schedule.initial_lockup_days * BLOCKS_PER_DAY + i * blocks_per_month; + let adjusted_monthly_tokens = + if let Some(payout) = early_payouts_summed.iter().find(|item| item.taken_from_period == i) { + if payout.value.uT().as_u64() >= monthly_tokens { + return Err(format!( + "upfront 'FromCadence' payout exceeds allocated monthly payout {}, allocated: {}, \ + early payout {}", + i, + MicroMinotari::from(monthly_tokens), + payout.value.uT() + )); + } + monthly_tokens - payout.value.uT().as_u64() + } else { + monthly_tokens + }; values_with_maturity.push(PreMineItem { - value: MicroMinotari::from(monthly_tokens), + value: MicroMinotari::from(adjusted_monthly_tokens), maturity, - fail_safe_height: schedule.expected_payout_period_blocks, + original_maturity: maturity, + fail_safe_height: maturity + schedule.expected_payout_period_blocks, beneficiary: apportionment.beneficiary.clone(), }); } let last_tokens = tokens_value - total_tokens; + let adjusted_last_tokens = if let Some(payout) = early_payouts_summed + .iter() + .find(|item| item.taken_from_period == schedule.monthly_fraction_denominator - 1) + { + if payout.value.uT().as_u64() >= last_tokens { + return Err(format!( + "upfront 'FromCadence' payout exceeds allocated monthly payout {}, allocated: {}, early \ + payout {}", + schedule.monthly_fraction_denominator - 1, + MicroMinotari::from(last_tokens), + payout.value.uT() + )); + } + last_tokens - payout.value.uT().as_u64() + } else { + last_tokens + }; + maturity += blocks_per_month; values_with_maturity.push(PreMineItem { - value: MicroMinotari::from(last_tokens), - maturity: maturity + blocks_per_month, - fail_safe_height: schedule.expected_payout_period_blocks, + value: MicroMinotari::from(adjusted_last_tokens), + maturity, + original_maturity: maturity, + fail_safe_height: maturity + schedule.expected_payout_period_blocks, beneficiary: apportionment.beneficiary.clone(), }); } @@ -386,7 +925,7 @@ pub async fn create_pre_mine_genesis_block_info( let mut public_keys = public_keys.clone(); public_keys.shuffle(&mut thread_rng()); let script = script!( - CheckHeight(item.maturity + item.fail_safe_height) LeZero + CheckHeight(item.fail_safe_height) LeZero IfThen CheckMultiSigVerifyAggregatePubKey(signature_threshold, address_len, public_keys.clone(), Box::new(commitment_bytes)) Else @@ -445,17 +984,20 @@ mod test { use crate::{ blocks::pre_mine::{ + contributors_upfront_release, create_pre_mine_genesis_block_info, create_pre_mine_output_values, - get_expected_payout_period_blocks, + get_expected_payout_grace_period_blocks, get_pre_mine_value, get_signature_threshold, get_tokenomics_pre_mine_unlock_schedule, verify_script_keys_for_index, Apportionment, + CustomRelease, PreMineItem, + ProportionalRelease, ReleaseCadence, - UpfrontRelease, + ReleaseStrategy, BLOCKS_PER_DAY, }, consensus::consensus_constants::MAINNET_PRE_MINE_VALUE, @@ -514,7 +1056,7 @@ mod test { // Only run this when you want to create a new utxo file #[ignore] #[tokio::test] - async fn print_pre_mine() { + async fn print_pre_mine_genesis_block_test_info() { let schedule = get_tokenomics_pre_mine_unlock_schedule(Network::MainNet); let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap(); let (outputs, kernel, _, _) = genesis_block_test_info(&pre_mine_items).await; @@ -540,7 +1082,43 @@ mod test { ); } + #[ignore] + #[tokio::test] + async fn print_pre_mine_list() { + let schedule = get_tokenomics_pre_mine_unlock_schedule(Network::MainNet); + let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap(); + let base_dir = dirs_next::document_dir().unwrap(); + let file_path = base_dir.join("tari_pre_mine").join("create").join("pre_mine_items.csv"); + if let Some(path) = file_path.parent() { + if !path.exists() { + fs::create_dir_all(path).unwrap(); + } + } + let mut file_stream = File::create(&file_path).expect("Could not create 'utxos.json'"); + + file_stream + .write_all("index,value,maturity,original_maturity,fail_safe_height,beneficiary\n".as_bytes()) + .unwrap(); + for (index, item) in pre_mine_items.iter().enumerate() { + file_stream + .write_all( + format!( + "{},{},{},{},{},{}\n", + index, + item.value, + item.maturity, + item.original_maturity, + item.fail_safe_height, + item.beneficiary, + ) + .as_bytes(), + ) + .unwrap(); + } + } + #[test] + #[allow(clippy::too_many_lines)] fn test_get_tokenomics_pre_mine_unlock_schedule() { for network in [ Network::LocalNet, @@ -562,60 +1140,83 @@ mod test { assert_eq!(schedule.network_rewards, Apportionment { beneficiary: "network_rewards".to_string(), percentage: 70, - tokens_amount: 14_700_000_000, + tokens_amount: 14_700_000_000.into(), schedule: None, }); assert_eq!(schedule.protocol, Apportionment { beneficiary: "protocol".to_string(), percentage: 9, - tokens_amount: 1_890_000_000, + tokens_amount: 1_890_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 180, monthly_fraction_denominator: 48, - upfront_release: Some(UpfrontRelease { - percentage: 40, - number_of_tokens: 20, - custom_upfront_tokens: vec![(1, 0), (1, 0), (1, 129_600), (1, 129_600)], - }), + upfront_release: vec![ + ReleaseStrategy::Proportional(ProportionalRelease { + percentage: 40, + number_of_tokens: 20, + }), + ReleaseStrategy::Custom({ + vec![ + CustomRelease { + value: 1.into(), + maturity: 0, + }, + CustomRelease { + value: 1.into(), + maturity: 0, + }, + CustomRelease { + value: 1.into(), + maturity: 129_600, + }, + CustomRelease { + value: 1.into(), + maturity: 129_600, + }, + ] + }), + ], expected_payout_period_blocks, }), }); - assert_eq!( - schedule.protocol.tokens_amount * - schedule.protocol.schedule.unwrap().upfront_release.unwrap().percentage / - 100, - 756_000_000 - ); + let percentage = if let ReleaseStrategy::Proportional(release) = + &schedule.protocol.schedule.unwrap().upfront_release[0] + { + release.percentage + } else { + panic!("Expected ReleaseStrategy::Proportional"); + }; + assert_eq!(schedule.protocol.tokens_amount * percentage / 100, 756_000_000.into()); assert_eq!(schedule.community, Apportionment { beneficiary: "community".to_string(), percentage: 5, - tokens_amount: 1_050_000_000, + tokens_amount: 1_050_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 180, monthly_fraction_denominator: 12, - upfront_release: None, + upfront_release: vec![], expected_payout_period_blocks, }), }); assert_eq!(schedule.contributors, Apportionment { beneficiary: "contributors".to_string(), percentage: 4, - tokens_amount: 840_000_000, + tokens_amount: 840_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 365, monthly_fraction_denominator: 60, - upfront_release: None, + upfront_release: contributors_upfront_release(), expected_payout_period_blocks, }), }); assert_eq!(schedule.participants, Apportionment { beneficiary: "participants".to_string(), percentage: 12, - tokens_amount: 2_520_000_000, + tokens_amount: 2_520_000_000.into(), schedule: Some(ReleaseCadence { initial_lockup_days: 365, monthly_fraction_denominator: 24, - upfront_release: None, + upfront_release: vec![], expected_payout_period_blocks, }), }); @@ -635,7 +1236,7 @@ mod test { schedule.community.tokens_amount + schedule.protocol.tokens_amount + schedule.network_rewards.tokens_amount, - 21_000_000_000 + 21_000_000_000.into() ); } } @@ -655,6 +1256,27 @@ mod test { } } + #[test] + fn test_pre_mine_fail_safe_height() { + for network in [ + Network::LocalNet, + Network::MainNet, + Network::Esmeralda, + Network::Igor, + Network::NextNet, + Network::StageNet, + ] { + let schedule = get_tokenomics_pre_mine_unlock_schedule(network); + let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap(); + for item in pre_mine_items { + assert_eq!( + item.fail_safe_height, + item.original_maturity + get_expected_payout_grace_period_blocks(network) + ); + } + } + } + #[test] fn test_create_pre_mine_output_values() { let schedule = get_tokenomics_pre_mine_unlock_schedule(Network::default()); @@ -667,9 +1289,9 @@ mod test { schedule.community.tokens_amount + schedule.contributors.tokens_amount + schedule.participants.tokens_amount; - let total_value = MicroMinotari::from(total_tokens * 1_000_000); + let total_value = MicroMinotari::from(total_tokens); assert_eq!( - total_pre_mine_value + MicroMinotari::from(schedule.network_rewards.tokens_amount * 1_000_000), + total_pre_mine_value + MicroMinotari::from(schedule.network_rewards.tokens_amount), total_value ); let protocol_tokens = pre_mine_items @@ -677,31 +1299,54 @@ mod test { .filter(|item| item.beneficiary == "protocol") .map(|item| item.value) .sum::(); - assert_eq!( - protocol_tokens, - MicroMinotari::from(schedule.protocol.tokens_amount * 1_000_000) - ); + assert_eq!(protocol_tokens, MicroMinotari::from(schedule.protocol.tokens_amount)); + let protocol_tokens_at_start = pre_mine_items .iter() .filter(|item| item.beneficiary == "protocol" && item.maturity == 0) .map(|item| item.value) .sum::(); assert_eq!(protocol_tokens_at_start, MicroMinotari::from(756_000_002 * 1_000_000)); + let community_tokens_at_start = pre_mine_items + .iter() + .filter(|item| item.beneficiary == "community" && item.maturity == 0) + .map(|item| item.value) + .sum::(); + assert_eq!(community_tokens_at_start, MicroMinotari::zero()); + let contributors_tokens_at_start = pre_mine_items + .iter() + .filter(|item| item.beneficiary == "contributors" && item.maturity == 0) + .map(|item| item.value) + .sum::(); + assert_eq!( + contributors_tokens_at_start, + MicroMinotari::from(388_525_385 * 1_000_000) + ); + let participants_tokens_at_start = pre_mine_items + .iter() + .filter(|item| item.beneficiary == "participants" && item.maturity == 0) + .map(|item| item.value) + .sum::(); + assert_eq!(participants_tokens_at_start, MicroMinotari::zero()); let all_tokens_at_start = pre_mine_items .iter() .filter(|item| item.maturity == 0) .map(|item| item.value) .sum::(); - assert_eq!(all_tokens_at_start, MicroMinotari::from(756_000_002 * 1_000_000)); + assert_eq!( + all_tokens_at_start, + protocol_tokens_at_start + + community_tokens_at_start + + contributors_tokens_at_start + + participants_tokens_at_start + ); + let community_tokens = pre_mine_items .iter() .filter(|item| item.beneficiary == "community") .map(|item| item.value) .sum::(); - assert_eq!( - community_tokens, - MicroMinotari::from(schedule.community.tokens_amount * 1_000_000) - ); + assert_eq!(community_tokens, MicroMinotari::from(schedule.community.tokens_amount)); let contributors_tokens = pre_mine_items .iter() .filter(|item| item.beneficiary == "contributors") @@ -709,7 +1354,7 @@ mod test { .sum::(); assert_eq!( contributors_tokens, - MicroMinotari::from(schedule.contributors.tokens_amount * 1_000_000) + MicroMinotari::from(schedule.contributors.tokens_amount) ); let participants_tokens = pre_mine_items .iter() @@ -718,7 +1363,7 @@ mod test { .sum::(); assert_eq!( participants_tokens, - MicroMinotari::from(schedule.participants.tokens_amount * 1_000_000) + MicroMinotari::from(schedule.participants.tokens_amount) ); } @@ -737,7 +1382,7 @@ mod test { let (outputs, kernel, threshold_spend_keys, backup_spend_keys) = genesis_block_test_info(&pre_mine_items).await; assert!(kernel.verify_signature().is_ok()); - let fail_safe_height = get_expected_payout_period_blocks(network); + let grace_period = get_expected_payout_grace_period_blocks(network); for (index, (output, (pre_mine_item, (threshold_keys, backup_key)))) in outputs .iter() .zip( @@ -768,7 +1413,7 @@ mod test { } else { panic!("Expected PushPubKey opcode in script at index {}", index); }; - assert_eq!(script_height, pre_mine_item.maturity + fail_safe_height); + assert_eq!(script_height, pre_mine_item.original_maturity + grace_period); assert_eq!(output.features.maturity, pre_mine_item.maturity); assert!(verify_script_keys_for_index( index, diff --git a/base_layer/core/src/transactions/tari_amount.rs b/base_layer/core/src/transactions/tari_amount.rs index b48451aab5..23f44702f0 100644 --- a/base_layer/core/src/transactions/tari_amount.rs +++ b/base_layer/core/src/transactions/tari_amount.rs @@ -264,7 +264,7 @@ impl Sub for MicroMinotari { } /// A convenience struct for representing full Tari. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd)] pub struct Minotari(MicroMinotari); newtype_ops! { [Minotari] {add sub mul div} {:=} Self Self } @@ -308,6 +308,11 @@ impl Minotari { let d = Decimal::from_parts(u128::from(self.0.as_u64()), 6, false).unwrap(); format!("{} T", format_currency(&d.to_string(), sep)) } + + #[allow(non_snake_case)] + pub fn uT(&self) -> MicroMinotari { + self.0 + } } impl From for Minotari { diff --git a/base_layer/wallet/src/output_manager_service/handle.rs b/base_layer/wallet/src/output_manager_service/handle.rs index 8d4cc02106..80ec75e3bf 100644 --- a/base_layer/wallet/src/output_manager_service/handle.rs +++ b/base_layer/wallet/src/output_manager_service/handle.rs @@ -72,6 +72,7 @@ pub enum OutputManagerRequest { metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, + original_maturity: u64, }, SpendBackupPreMineUtxo { tx_id: TxId, @@ -173,13 +174,15 @@ impl fmt::Display for OutputManagerRequest { tx_id, output_hash, expected_commitment, + original_maturity, .. } => write!( f, - "Encumber aggregate utxo with tx_id: {} and output: ({},{})", + "Encumber aggregate utxo with tx_id: {} and output: ({},{}) with original maturity: {}", tx_id, expected_commitment.to_hex(), - output_hash + output_hash, + original_maturity, ), SpendBackupPreMineUtxo { tx_id, @@ -813,6 +816,7 @@ impl OutputManagerHandle { metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, + original_maturity: u64, ) -> Result< ( Transaction, @@ -837,6 +841,7 @@ impl OutputManagerHandle { metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, + original_maturity, }) .await?? { diff --git a/base_layer/wallet/src/output_manager_service/service.rs b/base_layer/wallet/src/output_manager_service/service.rs index 5f12775b3a..b02c2edda6 100644 --- a/base_layer/wallet/src/output_manager_service/service.rs +++ b/base_layer/wallet/src/output_manager_service/service.rs @@ -257,6 +257,7 @@ where metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, + original_maturity, } => self .encumber_aggregate_utxo( tx_id, @@ -270,7 +271,7 @@ where dh_shared_secret_shares, recipient_address, PaymentId::Empty, - 0, + original_maturity, RangeProofType::BulletProofPlus, 0.into(), ) @@ -1251,7 +1252,7 @@ where dh_shared_secret_shares: Vec, recipient_address: TariAddress, payment_id: PaymentId, - maturity: u64, + original_maturity: u64, range_proof_type: RangeProofType, minimum_value_promise: MicroMinotari, ) -> Result< @@ -1365,7 +1366,7 @@ where // The entire input will be spent to a single recipient with no change let output_features = OutputFeatures { - maturity, + maturity: original_maturity, range_proof_type, ..Default::default() }; diff --git a/base_layer/wallet/src/transaction_service/handle.rs b/base_layer/wallet/src/transaction_service/handle.rs index 5768b34c3b..39fa8f6905 100644 --- a/base_layer/wallet/src/transaction_service/handle.rs +++ b/base_layer/wallet/src/transaction_service/handle.rs @@ -112,6 +112,7 @@ pub enum TransactionServiceRequest { metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, + original_maturity: u64, }, SpendBackupPreMineUtxo { fee_per_gram: MicroMinotari, @@ -253,11 +254,13 @@ impl fmt::Display for TransactionServiceRequest { metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, + original_maturity, .. } => f.write_str(&format!( "Creating encumber n-of-m utxo with: fee_per_gram = {}, output_hash = {}, commitment = {}, \ - script_input_shares = {:?},, script_signature_shares = {:?}, sender_offset_public_key_shares = {:?}, \ - metadata_ephemeral_public_key_shares = {:?}, dh_shared_secret_shares = {:?}, recipient_address = {}", + script_input_shares = {:?}, script_signature_shares = {:?}, sender_offset_public_key_shares = {:?}, \ + metadata_ephemeral_public_key_shares = {:?}, dh_shared_secret_shares = {:?}, recipient_address = {}, \ + original_maturity: {}", fee_per_gram, output_hash, expected_commitment.to_hex(), @@ -287,6 +290,7 @@ impl fmt::Display for TransactionServiceRequest { .map(|v| v.to_hex()) .collect::>(), recipient_address, + original_maturity, )), Self::FetchUnspentOutputs { output_hashes } => { write!( @@ -751,6 +755,7 @@ impl TransactionServiceHandle { metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, + original_maturity: u64, ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), TransactionServiceError> { match self .handle @@ -764,6 +769,7 @@ impl TransactionServiceHandle { metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, + original_maturity, }) .await?? { diff --git a/base_layer/wallet/src/transaction_service/service.rs b/base_layer/wallet/src/transaction_service/service.rs index 58c7bd1c1e..e1ef3ca9c1 100644 --- a/base_layer/wallet/src/transaction_service/service.rs +++ b/base_layer/wallet/src/transaction_service/service.rs @@ -710,6 +710,7 @@ where metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, + original_maturity, } => self .encumber_aggregate_tx( fee_per_gram, @@ -721,6 +722,7 @@ where metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address, + original_maturity, ) .await .map( @@ -1205,6 +1207,7 @@ where metadata_ephemeral_public_key_shares: Vec, dh_shared_secret_shares: Vec, recipient_address: TariAddress, + original_maturity: u64, ) -> Result<(TxId, Transaction, PublicKey, PublicKey, PublicKey), TransactionServiceError> { let tx_id = TxId::new_random(); @@ -1222,6 +1225,7 @@ where metadata_ephemeral_public_key_shares, dh_shared_secret_shares, recipient_address.clone(), + original_maturity, ) .await { From 7f3ccc1fc756abda9c36f2987e0dd44de6298ff2 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal Date: Tue, 1 Oct 2024 11:51:57 +0200 Subject: [PATCH 2/2] add pre-mine input from file --- .../src/automation/commands.rs | 142 +++++++++++++++--- .../src/automation/mod.rs | 1 + .../minotari_console_wallet/src/cli.rs | 8 + .../src/wallet_modes.rs | 5 +- base_layer/core/src/blocks/genesis_block.rs | 17 ++- 5 files changed, 139 insertions(+), 34 deletions(-) diff --git a/applications/minotari_console_wallet/src/automation/commands.rs b/applications/minotari_console_wallet/src/automation/commands.rs index 8d403f3410..f14225dd51 100644 --- a/applications/minotari_console_wallet/src/automation/commands.rs +++ b/applications/minotari_console_wallet/src/automation/commands.rs @@ -27,7 +27,7 @@ use std::{ fs, fs::File, io, - io::{LineWriter, Write}, + io::{BufRead, BufReader, LineWriter, Write}, path::{Path, PathBuf}, str::FromStr, time::{Duration, Instant}, @@ -84,6 +84,7 @@ use tari_core::{ Transaction, TransactionInput, TransactionInputVersion, + TransactionKernel, TransactionOutput, TransactionOutputVersion, UnblindedOutput, @@ -860,16 +861,16 @@ pub async fn command_runner( let mut recipient_info = Vec::new(); for item in args_recipient_info { - let embedded_outputs = match get_embedded_pre_mine_outputs(item.output_indexes.clone()) { - Ok(outputs) => outputs, - Err(e) => { - eprintln!("\nError: {}\n", e); - break; - }, - }; - let output_hashes = embedded_outputs.iter().map(|v| v.hash()).collect::>(); + if args.verify_unspent_outputs && !args.use_pre_mine_input_file { + let embedded_outputs = match get_embedded_pre_mine_outputs(item.output_indexes.clone(), None) { + Ok(outputs) => outputs, + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; + let output_hashes = embedded_outputs.iter().map(|v| v.hash()).collect::>(); - if args.verify_unspent_outputs { let unspent_outputs = transaction_service.fetch_unspent_outputs(output_hashes.clone()).await?; if unspent_outputs.len() != output_hashes.len() { let unspent_output_hashes = unspent_outputs.iter().map(|v| v.hash()).collect::>(); @@ -904,6 +905,7 @@ pub async fn command_runner( session_id: session_id.clone(), fee_per_gram: args.fee_per_gram, recipient_info, + use_pre_mine_input_file: args.use_pre_mine_input_file, }; let out_file = out_dir.join(get_file_name(SPEND_SESSION_INFO, None)); @@ -928,7 +930,7 @@ pub async fn command_runner( }, } - let embedded_output = match get_embedded_pre_mine_outputs(vec![args.output_index]) { + let embedded_output = match get_embedded_pre_mine_outputs(vec![args.output_index], None) { Ok(outputs) => outputs[0].clone(), Err(e) => { eprintln!("\nError: {}\n", e); @@ -1013,10 +1015,22 @@ pub async fn command_runner( break; } + let pre_mine_from_file = + match read_genesis_file_outputs(session_info.use_pre_mine_input_file, args.pre_mine_file_path) { + Ok(outputs) => outputs, + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; + let mut outputs_for_leader = Vec::with_capacity(args_recipient_info.len()); let mut outputs_for_self = Vec::with_capacity(args_recipient_info.len()); for recipient_info in &args_recipient_info { - let embedded_outputs = match get_embedded_pre_mine_outputs(recipient_info.output_indexes.clone()) { + let embedded_outputs = match get_embedded_pre_mine_outputs( + recipient_info.output_indexes.clone(), + pre_mine_from_file.clone(), + ) { Ok(outputs) => outputs, Err(e) => { eprintln!("\nError: {}\n", e); @@ -1175,6 +1189,15 @@ pub async fn command_runner( party_info_per_index.push(outputs_per_index); } + let pre_mine_from_file = + match read_genesis_file_outputs(session_info.use_pre_mine_input_file, args.pre_mine_file_path) { + Ok(outputs) => outputs, + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; + // Encumber outputs let mut outputs_for_parties = Vec::with_capacity(party_info_per_index.len()); let mut outputs_for_self = Vec::with_capacity(party_info_per_index.len()); @@ -1217,13 +1240,14 @@ pub async fn command_runner( } let original_maturity = pre_mine_items[current_index].original_maturity; - let embedded_output = match get_embedded_pre_mine_outputs(vec![current_index]) { - Ok(outputs) => outputs[0].clone(), - Err(e) => { - eprintln!("\nError: {}\n", e); - break; - }, - }; + let embedded_output = + match get_embedded_pre_mine_outputs(vec![current_index], pre_mine_from_file.clone()) { + Ok(outputs) => outputs[0].clone(), + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; match encumber_aggregate_utxo( transaction_service.clone(), @@ -1345,13 +1369,25 @@ pub async fn command_runner( break; } + let pre_mine_from_file = + match read_genesis_file_outputs(session_info.use_pre_mine_input_file, args.pre_mine_file_path) { + Ok(outputs) => outputs, + Err(e) => { + eprintln!("\nError: {}\n", e); + break; + }, + }; + let mut outputs_for_leader = Vec::with_capacity(party_info_indexed.outputs_for_self.len()); for (leader_info, party_info) in leader_info_indexed .outputs_for_parties .iter() .zip(party_info_indexed.outputs_for_self.iter()) { - let embedded_output = match get_embedded_pre_mine_outputs(vec![party_info.output_index]) { + let embedded_output = match get_embedded_pre_mine_outputs( + vec![party_info.output_index], + pre_mine_from_file.clone(), + ) { Ok(outputs) => outputs[0].clone(), Err(e) => { eprintln!("\nError: {}\n", e); @@ -1598,7 +1634,7 @@ pub async fn command_runner( } if args.save_to_file { - let file_name = get_pre_mine_file_name(); + let file_name = get_pre_mine_addition_file_name(); let out_dir_path = out_dir(&args.session_id)?; let out_file = out_dir_path.join(&file_name); let mut file_stream = match File::create(&out_file) { @@ -2352,7 +2388,58 @@ pub async fn command_runner( Ok(()) } +fn read_genesis_file_outputs( + use_pre_mine_input_file: bool, + pre_mine_file_path: Option, +) -> Result>, String> { + if use_pre_mine_input_file { + let file_path = if let Some(path) = pre_mine_file_path { + let file = path.join(get_pre_mine_file_name()); + if !file.exists() { + return Err(format!("Pre-mine file '{}' does not exist!", file.display())); + } + file + } else { + return Err("Missing pre-mine file!".to_string()); + }; + + let file = File::open(file_path.clone()) + .map_err(|e| format!("Problem opening file '{}' ({})", file_path.display(), e))?; + let reader = BufReader::new(file); + + let mut outputs = Vec::new(); + for line in reader.lines() { + let line = line.map_err(|e| format!("Problem reading line in file '{}' ({})", file_path.display(), e))?; + if let Ok(output) = serde_json::from_str::(&line) { + outputs.push(output); + } else if serde_json::from_str::(&line).is_ok() { + // Do nothing here + } else { + return Err(format!("Error: Could not deserialize line: {}", line)); + } + } + if outputs.is_empty() { + return Err(format!("No outputs found in '{}'", file_path.display())); + } + + Ok(Some(outputs)) + } else { + Ok(None) + } +} + fn get_pre_mine_file_name() -> String { + match Network::get_current_or_user_setting_or_default() { + Network::MainNet => "mainnet_pre_mine.json".to_string(), + Network::StageNet => "stagenet_pre_mine.json".to_string(), + Network::NextNet => "nextnet_pre_mine.json".to_string(), + Network::LocalNet => "esmeralda_pre_mine.json".to_string(), + Network::Igor => "igor_pre_mine.json".to_string(), + Network::Esmeralda => "esmeralda_pre_mine.json".to_string(), + } +} + +fn get_pre_mine_addition_file_name() -> String { match Network::get_current_or_user_setting_or_default() { Network::MainNet => "mainnet_pre_mine_addition.json".to_string(), Network::StageNet => "stagenet_pre_mine_addition.json".to_string(), @@ -2388,14 +2475,21 @@ fn sort_args_recipient_info(recipient_info: Vec) -> Vec) -> Result, CommandError> { - let utxos = get_all_embedded_pre_mine_outputs()?; +fn get_embedded_pre_mine_outputs( + output_indexes: Vec, + utxos: Option>, +) -> Result, CommandError> { + let utxos = if let Some(val) = utxos { + val + } else { + get_all_embedded_pre_mine_outputs()? + }; let mut fetched_outputs = Vec::with_capacity(output_indexes.len()); for index in output_indexes { if index >= utxos.len() { return Err(CommandError::PreMine(format!( - "Error: Invalid 'output_index' {} provided pre-mine outputs only number {}!", + "Error: Invalid 'output_index' {} provided, pre-mine outputs only number {}!", index, utxos.len() ))); diff --git a/applications/minotari_console_wallet/src/automation/mod.rs b/applications/minotari_console_wallet/src/automation/mod.rs index bba470a7be..8bb67bc39e 100644 --- a/applications/minotari_console_wallet/src/automation/mod.rs +++ b/applications/minotari_console_wallet/src/automation/mod.rs @@ -45,6 +45,7 @@ struct PreMineSpendStep1SessionInfo { session_id: String, fee_per_gram: MicroMinotari, recipient_info: Vec, + use_pre_mine_input_file: bool, } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index ae0baddbc9..6710ebb0ad 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -194,6 +194,8 @@ pub struct PreMineSpendSessionInfoArgs { pub recipient_info: Vec, #[clap(long)] pub verify_unspent_outputs: bool, + #[clap(long)] + pub use_pre_mine_input_file: bool, } #[derive(Debug, Args, Clone)] @@ -248,6 +250,8 @@ pub struct PreMineSpendPartyDetailsArgs { #[clap(long)] pub input_file: PathBuf, #[clap(long)] + pub pre_mine_file_path: Option, + #[clap(long)] pub recipient_info: Vec, #[clap(long)] pub alias: String, @@ -259,12 +263,16 @@ pub struct PreMineSpendEncumberAggregateUtxoArgs { pub session_id: String, #[clap(long)] pub input_file_names: Vec, + #[clap(long)] + pub pre_mine_file_path: Option, } #[derive(Debug, Args, Clone)] pub struct PreMineSpendInputOutputSigArgs { #[clap(long)] pub session_id: String, + #[clap(long)] + pub pre_mine_file_path: Option, } #[derive(Debug, Args, Clone)] diff --git a/applications/minotari_console_wallet/src/wallet_modes.rs b/applications/minotari_console_wallet/src/wallet_modes.rs index 5ff229b273..d1370c584d 100644 --- a/applications/minotari_console_wallet/src/wallet_modes.rs +++ b/applications/minotari_console_wallet/src/wallet_modes.rs @@ -546,11 +546,12 @@ mod test { pre-mine-spend-session-info --fee-per-gram 2 \ --recipient-info=[1,123,313]:\ f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA \ - --verify-unspent-outputs + --verify-unspent-outputs --use-pre-mine-input-file pre-mine-spend-party-details --input-file ./step_1_session_info.txt --alias alice \ --recipient-info=[1,123,313]:\ - f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA + f4LR9f6WwwcPiKJjK5ciTkU1ocNhANa3FPw1wkyVUwbuKpgiihawCXy6PFszunUWQ4Te8KVFnyWVHHwsk9x5Cg7ZQiA \ + --pre-mine-file-path ./pre_mine_file.txt pre-mine-spend-encumber-aggregate-utxo --session-id ee1643655c \ --input-file-names=step_2_for_leader_from_alice.txt --input-file-names=step_2_for_leader_from_bob.txt \ diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index ff0ffff376..cbf6b93f86 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -32,7 +32,10 @@ use tari_utilities::ByteArray; use crate::{ blocks::{block::Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock}, proof_of_work::{AccumulatedDifficulty, Difficulty, PowAlgorithm, PowData, ProofOfWork}, - transactions::{aggregated_body::AggregateBody, transaction_components::TransactionOutput}, + transactions::{ + aggregated_body::AggregateBody, + transaction_components::{TransactionKernel, TransactionOutput}, + }, OutputSmt, }; @@ -51,17 +54,15 @@ pub fn get_genesis_block(network: Network) -> ChainBlock { fn add_pre_mine_utxos_to_genesis_block(file: &str, block: &mut Block) { let mut utxos = Vec::new(); - let mut counter = 1; - let lines_count = file.lines().count(); for line in file.lines() { - if counter < lines_count { - let utxo: TransactionOutput = serde_json::from_str(line).unwrap(); + if let Ok(utxo) = serde_json::from_str::(line) { utxos.push(utxo); - } else { - block.body.add_kernel(serde_json::from_str(line).unwrap()); + } else if let Ok(kernel) = serde_json::from_str::(line) { + block.body.add_kernel(kernel); block.header.kernel_mmr_size += 1; + } else { + panic!("Error: Could not deserialize line: {} in file: {}", line, file); } - counter += 1; } block.header.output_smt_size += utxos.len() as u64; block.body.add_outputs(utxos);