use std::fmt::Display;
use std::str::FromStr;
use get_size2::GetSize;
use itertools::Itertools;
use neptune_consensus::transaction::Transaction;
use neptune_consensus::transaction::transaction_kernel::TransactionKernel;
use neptune_rpc_api::model::block::transaction_kernel::RpcTransactionKernelId;
use serde::Deserialize;
use serde::Serialize;
use tasm_lib::triton_vm::prelude::Digest;
use tasm_lib::triton_vm::prelude::Tip5;
use tasm_lib::twenty_first::prelude::MerkleTree;
#[derive(Debug, Clone, Copy, PartialEq, Eq, GetSize, Hash, Serialize, Deserialize)]
#[cfg_attr(any(test, feature = "test-helpers"), derive(Default))]
#[cfg_attr(any(test, feature = "arbitrary-impls"), derive(arbitrary::Arbitrary))]
pub struct TransactionKernelId(Digest);
impl Display for TransactionKernelId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0.to_hex())
}
}
impl FromStr for TransactionKernelId {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(Digest::try_from_hex(s)?))
}
}
impl From<TransactionKernelId> for Digest {
fn from(value: TransactionKernelId) -> Self {
value.0
}
}
impl From<TransactionKernelId> for RpcTransactionKernelId {
fn from(value: TransactionKernelId) -> Self {
RpcTransactionKernelId(value.0)
}
}
impl From<RpcTransactionKernelId> for TransactionKernelId {
fn from(value: RpcTransactionKernelId) -> Self {
TransactionKernelId(value.0)
}
}
impl TransactionKernelId {
pub(crate) fn combine(a: Self, b: Self) -> Self {
let a = a.0.values();
let b = b.0.values();
let c: Digest = Digest::new(std::array::from_fn(|i| a[i] + b[i]));
Self(c)
}
}
pub trait Txid {
fn txid(&self) -> TransactionKernelId;
}
impl Txid for TransactionKernel {
fn txid(&self) -> TransactionKernelId {
let mut index_set_hash = self
.inputs
.iter()
.map(|x| Tip5::hash(&x.absolute_indices))
.collect_vec();
index_set_hash.sort_unstable();
let index_set_hash = index_set_hash
.into_iter()
.flat_map(|x| x.values().to_vec())
.collect_vec();
let index_set_hash = Tip5::hash_varlen(&index_set_hash);
let output_hash = self
.outputs
.iter()
.flat_map(|x| x.canonical_commitment.values().to_vec())
.collect_vec();
let output_hash = Tip5::hash_varlen(&output_hash);
let announcements_hash = self
.announcements
.iter()
.flat_map(|x| Tip5::hash_varlen(&x.message).values().to_vec())
.collect_vec();
let announcements_hash = Tip5::hash_varlen(&announcements_hash);
let fee_hash = Tip5::hash(&self.fee);
let coinbase_hash = Tip5::hash(&self.coinbase);
let merge_bit_hash = Tip5::hash(&self.merge_bit);
let mut digests = vec![
index_set_hash,
output_hash,
announcements_hash,
fee_hash,
coinbase_hash,
merge_bit_hash,
];
while digests.len() & (digests.len() - 1) != 0 {
digests.push(Digest::default());
}
let as_digest = MerkleTree::par_new(&digests).unwrap().root();
TransactionKernelId(as_digest)
}
}
impl Txid for Transaction {
fn txid(&self) -> TransactionKernelId {
self.kernel.txid()
}
}
#[cfg(any(feature = "mock-rpc", test))]
impl rand::distr::Distribution<TransactionKernelId> for rand::distr::StandardUniform {
fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> TransactionKernelId {
TransactionKernelId(rng.random())
}
}
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use neptune_consensus::transaction::Transaction;
use neptune_consensus::transaction::primitive_witness::PrimitiveWitness;
use proptest::prelude::Strategy;
use proptest::prop_assert_eq;
use proptest::strategy::ValueTree;
use proptest::test_runner::TestRunner;
use proptest_arbitrary_interop::arb;
use test_strategy::proptest;
use super::*;
#[test]
fn txid_value_is_constant_under_transaction_update() {
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();
let tx_id_original = to_be_updated.kernel.txid();
let additions = mined.kernel.outputs.clone();
let removals = mined.kernel.inputs.clone();
let updated =
Transaction::new_with_primitive_witness_ms_data(to_be_updated, additions, removals);
assert_eq!(tx_id_original, updated.kernel.txid());
}
#[test]
fn transaction_kernel_id_from_hex() {
assert!(
TransactionKernelId::from_str(
"04e19a9adfefa811f68d8de45da6412d0d73368159a119af97cfd38da6cfc55ae7c6ba403b9c8b52"
)
.is_ok()
);
}
#[proptest]
fn combine_is_symmetric_and_deterministic(
#[strategy(arb())] a: TransactionKernelId,
#[strategy(arb())] b: TransactionKernelId,
) {
prop_assert_eq!(
TransactionKernelId::combine(a, b),
TransactionKernelId::combine(b, a)
);
}
}