Skip to content

Commit

Permalink
feat!: add input mr into genesis block (#6601)
Browse files Browse the repository at this point in the history
Description
---
- Added the input merkle root calculation into the genesis block;
previously, this was just a default hash, which is incorrect. This will
enable adding immediate spending of pre-mine outputs in the genesis
block.
- Added total kernel offset to the pre-mine output immediate spend
ceremony.
- Added total kernel offset to the pre-mine information added to the
genesis block.
- Added chain balance for immediate spend inputs and outputs in
`xxxx_genesis_sanity_check()` unit tests.

Motivation and Context
---
The genesis block did not cater for any immediate spend inputs.

How Has This Been Tested?
---
- Unit tests expanded & pass.
- System-level tests:
  - Performed a new pre-mine creation ceremony (_esmeralda_).
  - Performed a pre-mine immediate spend ceremony.
- Added the pre-mine and pre-mine immediate spend to the
_esmeralda_pre_mine,json_ embedded input file.
  - Unit test `fn esmeralda_genesis_sanity_check()` passes.

What process can a PR reviewer use to test or verify this change?
---
Code review.

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->

Breaking Changes
---

- [ ] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [X] Other - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
**BREAKING CHANGE:** The genesis block changed; this requires a
blockchain restart.
  • Loading branch information
hansieodendaal authored Oct 7, 2024
1 parent 755bf3e commit 27ffefa
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 99 deletions.
112 changes: 104 additions & 8 deletions applications/minotari_console_wallet/src/automation/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,11 @@ use tari_core::{
UnblindedOutput,
WalletOutput,
},
CryptoFactories,
},
};
use tari_crypto::{
commitment::HomomorphicCommitmentFactory,
dhke::DiffieHellmanSharedSecret,
ristretto::{pedersen::PedersenCommitment, RistrettoSecretKey},
};
Expand Down Expand Up @@ -1637,6 +1639,7 @@ pub async fn command_runner(
let mut inputs = Vec::new();
let mut outputs = Vec::new();
let mut kernels = Vec::new();
let mut kernel_offset = PrivateKey::default();
for (indexed_info, leader_self) in party_info_per_index.iter().zip(leader_info.outputs_for_self.iter())
{
let mut metadata_signatures = Vec::with_capacity(party_info_per_index.len());
Expand Down Expand Up @@ -1664,17 +1667,61 @@ pub async fn command_runner(
break;
}

// Collect all inputs, outputs and kernels that should go into the genesis block
if session_info.use_pre_mine_input_file {
match transaction_service.get_any_transaction(leader_self.tx_id).await {
Ok(Some(WalletTransaction::Completed(tx))) => {
for input in tx.transaction.body.inputs() {
inputs.push(input.clone());
// Fees must be zero
match tx.transaction.body.get_total_fee() {
Ok(fee) => {
if fee != MicroMinotari::zero() {
eprintln!(
"\nError: Transaction {} fee ({}) for does not equal zero!\n",
tx.tx_id, fee
);
break;
}
},
Err(e) => {
eprintln!("\nError: Transaction {}! ({})\n", tx.tx_id, e);
break;
},
}

let mut utxo_sum = Commitment::default();
for output in tx.transaction.body.outputs() {
outputs.push(output.clone());
utxo_sum = &utxo_sum + &output.commitment;
}
for input in tx.transaction.body.inputs() {
inputs.push(input.clone());
match input.commitment() {
Ok(commitment) => utxo_sum = &utxo_sum - commitment,
Err(e) => {
eprintln!("\nError: Input commitment ({})!\n", e);
break;
},
}
}
let mut kernel_sum = Commitment::default();
for kernel in tx.transaction.body.kernels() {
kernels.push(kernel.clone());
kernel_sum = &kernel_sum + &kernel.excess;
}
kernel_offset = &kernel_offset + &tx.transaction.offset;
// Ensure that the balance equation holds:
// sum(output commitments) - sum(input commitments) = sum(kernel excesses) +
// total_offset
let offset = CryptoFactories::default()
.commitment
.commit_value(&tx.transaction.offset, 0);
if utxo_sum != &kernel_sum + &offset {
eprintln!(
"\nError: Transaction {} balance: UTXO sum {} vs. kernel sum + offset {}!\n",
tx.tx_id,
utxo_sum.to_hex(),
(&kernel_sum + &offset).to_hex()
);
}
},
Ok(_) => {
Expand All @@ -1692,10 +1739,38 @@ pub async fn command_runner(
}
}

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);
if session_info.use_pre_mine_input_file {
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);
// Ensure that the balance equation holds:
// sum(output commitments) - sum(input commitments) = sum(kernel excesses) + kernel_offset
let mut utxo_sum = Commitment::default();
for output in &outputs {
utxo_sum = &utxo_sum + &output.commitment;
}
for input in &inputs {
match input.commitment() {
Ok(commitment) => utxo_sum = &utxo_sum - commitment,
Err(e) => {
eprintln!("\nError: Input commitment ({})!\n", e);
break;
},
}
}
let mut kernel_sum = Commitment::default();
for kernel in &kernels {
kernel_sum = &kernel_sum + &kernel.excess;
}
let offset = CryptoFactories::default().commitment.commit_value(&kernel_offset, 0);
if utxo_sum != &kernel_sum + &offset {
eprintln!(
"\nError: Transactions balance: UTXO sum {} vs. kernel sum + offset {}!\n",
utxo_sum.to_hex(),
(&kernel_sum + &offset).to_hex()
);
}

let mut file_stream = match File::create(&out_file) {
Ok(file) => file,
Err(e) => {
Expand All @@ -1705,7 +1780,8 @@ pub async fn command_runner(
};

let mut error = false;
for input in inputs {
inputs.sort();
for input in &inputs {
let input_s = match serde_json::to_string(&input) {
Ok(val) => val,
Err(e) => {
Expand All @@ -1723,7 +1799,8 @@ pub async fn command_runner(
if error {
break;
}
for output in outputs {
outputs.sort();
for output in &outputs {
let utxo_s = match serde_json::to_string(&output) {
Ok(val) => val,
Err(e) => {
Expand All @@ -1741,7 +1818,8 @@ pub async fn command_runner(
if error {
break;
}
for kernel in kernels {
kernels.sort();
for kernel in &kernels {
let kernel_s = match serde_json::to_string(&kernel) {
Ok(val) => val,
Err(e) => {
Expand All @@ -1758,9 +1836,27 @@ pub async fn command_runner(
if error {
break;
}
let kernel_offset_s = match serde_json::to_string(&kernel_offset) {
Ok(val) => val,
Err(e) => {
eprintln!("\nError: Could not serialize kernel offset ({})\n", e);
break;
},
};
if let Err(e) = file_stream.write_all(format!("{}\n", kernel_offset_s).as_bytes()) {
eprintln!("\nError: Could not write the genesis file ({})\n", e);
break;
}
}

println!();
if session_info.use_pre_mine_input_file {
println!(
"Genesis block immediate pre-mine spend information: '{}' in '{}'",
file_name,
out_dir_path.display()
);
}
println!("Concluded step 5 'pre-mine-spend-aggregate-transaction'");
println!();
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ use crate::{
common::{BanPeriod, BanReason},
transactions::transaction_components::TransactionError,
validation::ValidationError,
MrHashError,
};

#[derive(Debug, Error)]
Expand Down Expand Up @@ -100,6 +101,8 @@ pub enum HorizonSyncError {
SMTError(#[from] SMTError),
#[error("ByteArrayError error: {0}")]
ByteArrayError(String),
#[error("FixedHash size error: {0}")]
MrHashError(#[from] MrHashError),
}

impl From<ByteArrayError> for HorizonSyncError {
Expand Down Expand Up @@ -131,7 +134,8 @@ impl HorizonSyncError {
HorizonSyncError::ConnectivityError(_) |
HorizonSyncError::NoMoreSyncPeers(_) |
HorizonSyncError::PeerNotFound |
HorizonSyncError::JoinError(_) => None,
HorizonSyncError::JoinError(_) |
HorizonSyncError::MrHashError(_) => None,

// short ban
err @ HorizonSyncError::MaxLatencyExceeded { .. } |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use crate::{
chain_storage::{async_db::AsyncBlockchainDb, BlockchainBackend, ChainStorageError, MmrTree},
common::{rolling_avg::RollingAverageTime, BanPeriod},
consensus::ConsensusManager,
output_mr_hash_from_smt,
proto::base_node::{sync_utxos_response::Txo, SyncKernelsRequest, SyncUtxosRequest, SyncUtxosResponse},
transactions::transaction_components::{
transaction_output::batch_verify_range_proofs,
Expand Down Expand Up @@ -783,7 +784,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> {

// Helper function to check the output SMT root hash against the expected root hash.
fn check_output_smt_root_hash(output_smt: &mut OutputSmt, header: &BlockHeader) -> Result<(), HorizonSyncError> {
let root = FixedHash::try_from(output_smt.hash().as_slice())?;
let root = output_mr_hash_from_smt(output_smt)?;
if root != header.output_mr {
warn!(
target: LOG_TARGET,
Expand Down
Loading

0 comments on commit 27ffefa

Please sign in to comment.