use std::sync::Arc;
use neptune_primitives::mast_hash::MastHash;
use neptune_primitives::network::Network;
use neptune_primitives::timestamp::Timestamp;
use crate::block::mutator_set_update::MutatorSetUpdate;
use crate::proof_abstractions::tasm::program::TritonVmProofJobOptions;
use crate::proof_abstractions::triton_vm_job_queue::TritonVmJobQueue;
pub mod announcement;
pub mod lock_script;
pub mod primitive_witness;
#[cfg(any(test, feature = "test-helpers"))]
pub mod test_helpers;
pub mod transaction_kernel;
pub mod transaction_proof;
pub mod transparent_input;
pub mod transparent_transaction_info;
pub mod utxo;
pub mod utxo_triple;
pub mod validity;
use anyhow::ensure;
use anyhow::Result;
use get_size2::GetSize;
use itertools::Itertools;
use neptune_mutator_set::mutator_set_accumulator::MutatorSetAccumulator;
use num_bigint::BigInt;
use num_rational::BigRational;
use serde::Deserialize;
use serde::Serialize;
use tasm_lib::triton_vm::proof::Claim;
use tasm_lib::twenty_first::math::bfield_codec::BFieldCodec;
use tasm_lib::twenty_first::util_types::mmr::mmr_successor_proof::MmrSuccessorProof;
use tracing::info;
pub use transaction_proof::TransactionProof;
use validity::proof_collection::ProofCollection;
use validity::single_proof::SingleProofWitness;
use validity::tasm::single_proof::merge_branch::MergeWitness;
use validity::tasm::single_proof::update_branch::UpdateWitness;
use self::primitive_witness::PrimitiveWitness;
use self::transaction_kernel::TransactionKernel;
use self::transaction_kernel::TransactionKernelModifier;
use self::transaction_kernel::TransactionKernelProxy;
use super::consensus_rule_set::ConsensusRuleSet;
use crate::transaction::validity::neptune_proof::Proof;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, GetSize)]
pub struct Transaction {
pub kernel: TransactionKernel,
pub proof: TransactionProof,
}
impl From<PrimitiveWitness> for Transaction {
fn from(value: PrimitiveWitness) -> Self {
Transaction {
kernel: value.kernel.clone(),
proof: TransactionProof::Witness(value),
}
}
}
#[cfg(any(test, feature = "test-helpers"))]
impl From<Arc<Transaction>> for Transaction {
fn from(t: Arc<Transaction>) -> Self {
(*t).clone()
}
}
impl Transaction {
#[expect(clippy::too_many_arguments)]
pub async fn new_with_updated_mutator_set_records_given_proof(
old_transaction_kernel: TransactionKernel,
previous_mutator_set_accumulator: &MutatorSetAccumulator,
mutator_set_update: &MutatorSetUpdate,
old_single_proof: Proof,
triton_vm_job_queue: Arc<TritonVmJobQueue>,
proof_job_options: TritonVmProofJobOptions,
new_timestamp: Option<Timestamp>,
_consensus_rule_set: ConsensusRuleSet,
) -> anyhow::Result<Transaction> {
ensure!(
old_transaction_kernel.mutator_set_hash == previous_mutator_set_accumulator.hash(),
"Old transaction kernel's mutator set hash does not agree \
with supplied mutator set accumulator."
);
let addition_records = mutator_set_update.additions.clone();
let mut calculated_new_mutator_set = previous_mutator_set_accumulator.clone();
let mut new_inputs = old_transaction_kernel.inputs.clone();
mutator_set_update
.apply_to_accumulator_and_records(
&mut calculated_new_mutator_set,
&mut new_inputs.iter_mut().collect_vec(),
&mut [],
)
.unwrap_or_else(|_| panic!("Could not apply mutator set update."));
let aocl_successor_proof = MmrSuccessorProof::new_from_batch_append(
&previous_mutator_set_accumulator.aocl,
&addition_records
.iter()
.map(|addition_record| addition_record.canonical_commitment)
.collect_vec(),
);
let mut modifier = TransactionKernelModifier::default()
.inputs(new_inputs)
.mutator_set_hash(calculated_new_mutator_set.hash());
if let Some(new_timestamp) = new_timestamp {
modifier = modifier.timestamp(new_timestamp);
}
let new_kernel = modifier.clone_modify(&old_transaction_kernel);
let update_witness = UpdateWitness::from_old_transaction(
old_transaction_kernel,
old_single_proof,
previous_mutator_set_accumulator.clone(),
new_kernel.clone(),
calculated_new_mutator_set,
aocl_successor_proof,
);
info!("starting single proof via update ...");
let single_proof = SingleProofWitness::from_update(update_witness)
.produce(triton_vm_job_queue, proof_job_options)
.await?;
let proof = TransactionProof::SingleProof(single_proof);
info!("done.");
Ok(Transaction {
kernel: new_kernel,
proof,
})
}
pub async fn is_valid(&self, network: Network, consensus_rule_set: ConsensusRuleSet) -> bool {
let kernel_hash = self.kernel.mast_hash();
self.proof
.verify(kernel_hash, network, consensus_rule_set)
.await
}
pub async fn merge_with(
self,
other: Transaction,
shuffle_seed: [u8; 32],
triton_vm_job_queue: Arc<TritonVmJobQueue>,
proof_job_options: TritonVmProofJobOptions,
_consensus_rule_set: ConsensusRuleSet,
) -> Result<Transaction> {
assert_eq!(
self.kernel.mutator_set_hash, other.kernel.mutator_set_hash,
"Mutator sets must be equal for transaction merger."
);
assert!(
self.kernel.coinbase.is_none() && other.kernel.coinbase.is_none(),
"Don't use me for coinbase transactions, por favor"
);
let merge_witness = MergeWitness::from_transactions(self, other, shuffle_seed);
MergeWitness::merge(merge_witness, triton_vm_job_queue, proof_job_options).await
}
pub fn fee_density(&self) -> BigRational {
let transaction_as_bytes = bincode::serialize(&self).unwrap();
let transaction_size = BigInt::from(transaction_as_bytes.get_size());
let transaction_fee = self.kernel.fee.to_nau();
BigRational::new_raw(transaction_fee.into(), transaction_size)
}
pub fn is_confirmable_relative_to(
&self,
mutator_set_accumulator: &MutatorSetAccumulator,
) -> bool {
self.kernel
.is_confirmable_relative_to(mutator_set_accumulator)
.is_ok()
}
}
#[cfg(any(test, feature = "test-helpers"))]
impl Transaction {
pub fn new_with_primitive_witness_ms_data(
old_primitive_witness: PrimitiveWitness,
new_addition_records: Vec<neptune_mutator_set::addition_record::AdditionRecord>,
new_removal_records: Vec<neptune_mutator_set::removal_record::RemovalRecord>,
) -> Transaction {
let mutator_set_update = MutatorSetUpdate::new(new_removal_records, new_addition_records);
let primitive_witness =
PrimitiveWitness::update_with_new_ms_data(old_primitive_witness, mutator_set_update);
let kernel = primitive_witness.kernel.clone();
let witness = TransactionProof::Witness(primitive_witness);
Transaction {
kernel,
proof: witness,
}
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
pub(crate) mod tests {
use lock_script::LockScript;
use macro_rules_attr::apply;
use neptune_primitives::network::Network;
use neptune_primitives::timestamp::Timestamp;
use proptest::prelude::Strategy;
use proptest::test_runner::TestRunner;
use rand::random;
use tasm_lib::prelude::Digest;
use tests::primitive_witness::SaltedUtxos;
use tests::utxo::Utxo;
use tracing_test::traced_test;
use super::*;
use crate::proof_abstractions::test_runtime::shared_tokio_runtime;
use crate::transaction::test_helpers::make_mock_transaction;
use crate::transaction::utxo_triple::UtxoTriple;
use crate::transaction::validity::single_proof::produce_single_proof;
use crate::type_scripts::native_currency_amount::NativeCurrencyAmount;
impl Transaction {
pub(crate) fn new_single_proof(kernel: TransactionKernel, proof: Proof) -> Self {
Self {
kernel,
proof: TransactionProof::SingleProof(proof),
}
}
}
#[test]
fn decode_encode_test_empty() {
let empty_kernel = TransactionKernelProxy {
inputs: vec![],
outputs: vec![],
announcements: vec![],
fee: NativeCurrencyAmount::coins(0),
coinbase: None,
timestamp: Default::default(),
mutator_set_hash: Digest::default(),
merge_bit: false,
}
.into_kernel();
let primitive_witness = PrimitiveWitness {
input_utxos: SaltedUtxos::empty(),
type_scripts_and_witnesses: vec![],
lock_scripts_and_witnesses: vec![],
input_membership_proofs: vec![],
output_utxos: SaltedUtxos::empty(),
output_sender_randomnesses: vec![],
output_receiver_digests: vec![],
mutator_set_accumulator: MutatorSetAccumulator::default(),
kernel: empty_kernel,
};
let encoded = primitive_witness.encode();
let decoded = *PrimitiveWitness::decode(&encoded).unwrap();
assert_eq!(primitive_witness, decoded);
}
#[traced_test]
#[test]
fn tx_get_timestamp_test() {
let output_1 = Utxo::new_native_currency(
LockScript::anyone_can_spend().hash(),
NativeCurrencyAmount::coins(42),
);
let ar = UtxoTriple {
utxo: output_1.clone(),
sender_randomness: random(),
receiver_digest: random(),
}
.addition_record();
let coinbase_transaction = make_mock_transaction(vec![], vec![ar]);
assert!(Timestamp::now() - coinbase_transaction.kernel.timestamp < Timestamp::seconds(10));
}
#[traced_test]
#[apply(shared_tokio_runtime)]
async fn update_single_proof_works() {
async fn prop(
to_be_updated: PrimitiveWitness,
mined: PrimitiveWitness,
consensus_rule_set: ConsensusRuleSet,
) {
let network = Network::Main;
let as_single_proof = produce_single_proof(
&to_be_updated,
TritonVmJobQueue::get_instance(),
TritonVmProofJobOptions::default_with_network(network),
consensus_rule_set,
)
.await
.unwrap();
let original_tx = Transaction {
kernel: to_be_updated.kernel,
proof: TransactionProof::SingleProof(as_single_proof),
};
assert!(original_tx.is_valid(network, consensus_rule_set).await);
let mutator_set_update =
MutatorSetUpdate::new(mined.kernel.inputs.clone(), mined.kernel.outputs.clone());
let updated_tx = Transaction::new_with_updated_mutator_set_records_given_proof(
original_tx.kernel,
&to_be_updated.mutator_set_accumulator,
&mutator_set_update,
original_tx.proof.into_single_proof(),
TritonVmJobQueue::get_instance(),
TritonVmProofJobOptions::default_with_network(network),
None,
consensus_rule_set,
)
.await
.unwrap();
assert!(updated_tx.is_valid(network, consensus_rule_set).await)
}
let consensus_rule_set = ConsensusRuleSet::HardforkGamma;
for (to_be_updated_params, mined_params) in [
((4, 4, 4), (3, 3, 3)),
((1, 0, 1), (1, 1, 0)),
((1, 1, 0), (1, 0, 0)),
((6, 2, 1), (1, 1, 1)),
((2, 2, 2), (2, 2, 2)),
] {
println!("consensus_rule_set: {consensus_rule_set}");
println!("to_be_updated_params: {to_be_updated_params:?}");
println!("mined_params: {mined_params:?}");
let mut test_runner = TestRunner::deterministic();
let [to_be_updated, mined] =
PrimitiveWitness::arbitrary_tuple_with_matching_mutator_sets([
to_be_updated_params,
mined_params,
])
.new_tree(&mut test_runner)
.unwrap()
.current();
assert!(to_be_updated.validate().await.is_ok());
assert!(mined.validate().await.is_ok());
prop(to_be_updated.clone(), mined.clone(), consensus_rule_set).await;
}
}
#[traced_test]
#[apply(shared_tokio_runtime)]
async fn primitive_witness_update_properties() {
async fn assert_valid_as_pw(transaction: &Transaction) {
let TransactionProof::Witness(pw) = &transaction.proof else {
panic!("Expected primitive witness variant");
};
assert!(pw.validate().await.is_ok())
}
let mut test_runner = TestRunner::deterministic();
let [to_be_updated, mined] =
PrimitiveWitness::arbitrary_tuple_with_matching_mutator_sets([(4, 4, 4), (3, 3, 3)])
.new_tree(&mut test_runner)
.unwrap()
.current();
assert!(to_be_updated.validate().await.is_ok());
assert!(mined.validate().await.is_ok());
let updated_with_block = Transaction::new_with_primitive_witness_ms_data(
to_be_updated.clone(),
mined.kernel.outputs.clone(),
mined.kernel.inputs.clone(),
);
assert_valid_as_pw(&updated_with_block).await;
assert_eq!(
to_be_updated.kernel.coinbase,
updated_with_block.kernel.coinbase
);
assert_eq!(to_be_updated.kernel.fee, updated_with_block.kernel.fee);
assert_eq!(
to_be_updated.kernel.outputs,
updated_with_block.kernel.outputs
);
assert_eq!(
to_be_updated.kernel.announcements,
updated_with_block.kernel.announcements
);
assert_eq!(
to_be_updated.kernel.inputs.len(),
updated_with_block.kernel.inputs.len(),
);
assert_eq!(
to_be_updated
.kernel
.inputs
.iter()
.map(|x| x.absolute_indices)
.collect_vec(),
updated_with_block
.kernel
.inputs
.iter()
.map(|x| x.absolute_indices)
.collect_vec()
);
assert_ne!(
to_be_updated.kernel.mutator_set_hash,
updated_with_block.kernel.mutator_set_hash
);
}
}