use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use kaspa_core::hex::*;
use serde::{Deserialize, Serialize};
use smallvec::SmallVec;
use std::{collections::HashSet, fmt::Display, ops::Range};
use wasm_bindgen::prelude::*;
use workflow_wasm::jsvalue::JsValueTrait;
use crate::{
hashing,
subnets::{self, SubnetworkId},
};
pub const COINBASE_TRANSACTION_INDEX: usize = 0;
pub const SCRIPT_VECTOR_SIZE: usize = 36;
pub type TransactionId = kaspa_hashes::Hash;
pub type ScriptVec = SmallVec<[u8; SCRIPT_VECTOR_SIZE]>;
pub type ScriptPublicKeyVersion = u16;
pub use smallvec::smallvec as scriptvec;
pub type ScriptPublicKeys = HashSet<ScriptPublicKey>;
#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Hash)]
#[serde(rename_all = "camelCase")]
#[wasm_bindgen(inspectable)]
pub struct ScriptPublicKey {
pub version: ScriptPublicKeyVersion,
script: ScriptVec, }
impl ScriptPublicKey {
pub fn new(version: ScriptPublicKeyVersion, script: ScriptVec) -> Self {
Self { version, script }
}
pub fn from_vec(version: ScriptPublicKeyVersion, script: Vec<u8>) -> Self {
Self { version, script: ScriptVec::from_vec(script) }
}
pub fn version(&self) -> ScriptPublicKeyVersion {
self.version
}
pub fn script(&self) -> &[u8] {
&self.script
}
}
#[wasm_bindgen]
impl ScriptPublicKey {
#[wasm_bindgen(constructor)]
pub fn constructor(version: u16, script: JsValue) -> Result<ScriptPublicKey, JsError> {
let script = script.try_as_vec_u8()?;
Ok(ScriptPublicKey::new(version, script.into()))
}
#[wasm_bindgen(getter = script)]
pub fn script_as_hex(&self) -> String {
self.script.to_hex()
}
}
impl BorshSerialize for ScriptPublicKey {
fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
borsh::BorshSerialize::serialize(&self.version, writer)?;
borsh::BorshSerialize::serialize(&self.script.as_slice(), writer)?;
Ok(())
}
}
impl BorshDeserialize for ScriptPublicKey {
fn deserialize(buf: &mut &[u8]) -> std::io::Result<Self> {
Ok(Self::from_vec(borsh::BorshDeserialize::deserialize(buf)?, borsh::BorshDeserialize::deserialize(buf)?))
}
}
impl BorshSchema for ScriptPublicKey {
fn add_definitions_recursively(
definitions: &mut std::collections::HashMap<borsh::schema::Declaration, borsh::schema::Definition>,
) {
let fields = borsh::schema::Fields::NamedFields(std::vec![
("version".to_string(), <u16>::declaration()),
("script".to_string(), <Vec<u8>>::declaration())
]);
let definition = borsh::schema::Definition::Struct { fields };
Self::add_definition(Self::declaration(), definition, definitions);
<u16>::add_definitions_recursively(definitions);
<Vec<u8>>::add_definitions_recursively(definitions);
}
fn declaration() -> borsh::schema::Declaration {
"ScriptPublicKey".to_string()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)]
#[serde(rename_all = "camelCase")]
#[wasm_bindgen(inspectable, js_name = TxUtxoEntry)]
pub struct UtxoEntry {
pub amount: u64,
#[wasm_bindgen(js_name = scriptPublicKey, getter_with_clone)]
pub script_public_key: ScriptPublicKey,
#[wasm_bindgen(js_name = blockDaaScore)]
pub block_daa_score: u64,
#[wasm_bindgen(js_name = isCoinbase)]
pub is_coinbase: bool,
}
impl UtxoEntry {
pub fn new(amount: u64, script_public_key: ScriptPublicKey, block_daa_score: u64, is_coinbase: bool) -> Self {
Self { amount, script_public_key, block_daa_score, is_coinbase }
}
}
pub type TransactionIndexType = u32;
#[derive(Eq, Hash, PartialEq, Debug, Copy, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize, BorshSchema)]
#[serde(rename_all = "camelCase")]
pub struct TransactionOutpoint {
pub transaction_id: TransactionId,
pub index: TransactionIndexType,
}
impl TransactionOutpoint {
pub fn new(transaction_id: TransactionId, index: u32) -> Self {
Self { transaction_id, index }
}
}
impl Display for TransactionOutpoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.transaction_id, self.index)
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)]
#[serde(rename_all = "camelCase")]
pub struct TransactionInput {
pub previous_outpoint: TransactionOutpoint,
pub signature_script: Vec<u8>, pub sequence: u64,
pub sig_op_count: u8,
}
impl TransactionInput {
pub fn new(previous_outpoint: TransactionOutpoint, signature_script: Vec<u8>, sequence: u64, sig_op_count: u8) -> Self {
Self { previous_outpoint, signature_script, sequence, sig_op_count }
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)]
#[serde(rename_all = "camelCase")]
pub struct TransactionOutput {
pub value: u64,
pub script_public_key: ScriptPublicKey,
}
impl TransactionOutput {
pub fn new(value: u64, script_public_key: ScriptPublicKey) -> Self {
Self { value, script_public_key }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
pub version: u16,
pub inputs: Vec<TransactionInput>,
pub outputs: Vec<TransactionOutput>,
pub lock_time: u64,
pub subnetwork_id: SubnetworkId,
pub gas: u64,
pub payload: Vec<u8>,
id: TransactionId,
}
impl Transaction {
pub fn new(
version: u16,
inputs: Vec<TransactionInput>,
outputs: Vec<TransactionOutput>,
lock_time: u64,
subnetwork_id: SubnetworkId,
gas: u64,
payload: Vec<u8>,
) -> Self {
let mut tx = Self {
version,
inputs,
outputs,
lock_time,
subnetwork_id,
gas,
payload,
id: Default::default(), };
tx.finalize();
tx
}
}
impl Transaction {
pub fn is_coinbase(&self) -> bool {
self.subnetwork_id == subnets::SUBNETWORK_ID_COINBASE
}
pub fn finalize(&mut self) {
self.id = hashing::tx::id(self);
}
pub fn id(&self) -> TransactionId {
self.id
}
}
pub trait VerifiableTransaction {
fn tx(&self) -> &Transaction;
fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry);
fn populated_inputs(&self) -> PopulatedInputIterator<'_, Self>
where
Self: Sized,
{
PopulatedInputIterator::new(self)
}
fn inputs(&self) -> &[TransactionInput] {
&self.tx().inputs
}
fn outputs(&self) -> &[TransactionOutput] {
&self.tx().outputs
}
fn is_coinbase(&self) -> bool {
self.tx().is_coinbase()
}
fn id(&self) -> TransactionId {
self.tx().id()
}
}
pub struct PopulatedInputIterator<'a, T: VerifiableTransaction> {
tx: &'a T,
r: Range<usize>,
}
impl<'a, T: VerifiableTransaction> PopulatedInputIterator<'a, T> {
pub fn new(tx: &'a T) -> Self {
Self { tx, r: (0..tx.inputs().len()) }
}
}
impl<'a, T: VerifiableTransaction> Iterator for PopulatedInputIterator<'a, T> {
type Item = (&'a TransactionInput, &'a UtxoEntry);
fn next(&mut self) -> Option<Self::Item> {
self.r.next().map(|i| self.tx.populated_input(i))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.r.size_hint()
}
}
impl<'a, T: VerifiableTransaction> ExactSizeIterator for PopulatedInputIterator<'a, T> {}
pub struct PopulatedTransaction<'a> {
pub tx: &'a Transaction,
pub entries: Vec<UtxoEntry>,
}
impl<'a> PopulatedTransaction<'a> {
pub fn new(tx: &'a Transaction, entries: Vec<UtxoEntry>) -> Self {
assert_eq!(tx.inputs.len(), entries.len());
Self { tx, entries }
}
}
impl<'a> VerifiableTransaction for PopulatedTransaction<'a> {
fn tx(&self) -> &Transaction {
self.tx
}
fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
(&self.tx.inputs[index], &self.entries[index])
}
}
pub struct ValidatedTransaction<'a> {
pub tx: &'a Transaction,
pub entries: Vec<UtxoEntry>,
pub calculated_fee: u64,
}
impl<'a> ValidatedTransaction<'a> {
pub fn new(populated_tx: PopulatedTransaction<'a>, calculated_fee: u64) -> Self {
Self { tx: populated_tx.tx, entries: populated_tx.entries, calculated_fee }
}
pub fn new_coinbase(tx: &'a Transaction) -> Self {
assert!(tx.is_coinbase());
Self { tx, entries: Vec::new(), calculated_fee: 0 }
}
}
impl<'a> VerifiableTransaction for ValidatedTransaction<'a> {
fn tx(&self) -> &Transaction {
self.tx
}
fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
(&self.tx.inputs[index], &self.entries[index])
}
}
impl AsRef<Transaction> for Transaction {
fn as_ref(&self) -> &Transaction {
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct MutableTransaction<T: AsRef<Transaction> = std::sync::Arc<Transaction>> {
pub tx: T,
pub entries: Vec<Option<UtxoEntry>>,
pub calculated_fee: Option<u64>,
pub calculated_mass: Option<u64>,
}
impl<T: AsRef<Transaction>> MutableTransaction<T> {
pub fn new(tx: T) -> Self {
let num_inputs = tx.as_ref().inputs.len();
Self { tx, entries: vec![None; num_inputs], calculated_fee: None, calculated_mass: None }
}
pub fn id(&self) -> TransactionId {
self.tx.as_ref().id()
}
pub fn with_entries(tx: T, entries: Vec<UtxoEntry>) -> Self {
assert_eq!(tx.as_ref().inputs.len(), entries.len());
Self { tx, entries: entries.into_iter().map(Some).collect(), calculated_fee: None, calculated_mass: None }
}
pub fn as_verifiable(&self) -> impl VerifiableTransaction + '_ {
assert!(self.is_verifiable());
MutableTransactionVerifiableWrapper { inner: self }
}
pub fn is_verifiable(&self) -> bool {
assert_eq!(self.entries.len(), self.tx.as_ref().inputs.len());
self.entries.iter().all(|e| e.is_some())
}
pub fn is_fully_populated(&self) -> bool {
self.is_verifiable() && self.calculated_fee.is_some() && self.calculated_mass.is_some()
}
pub fn missing_outpoints(&self) -> impl Iterator<Item = TransactionOutpoint> + '_ {
assert_eq!(self.entries.len(), self.tx.as_ref().inputs.len());
self.entries.iter().enumerate().filter_map(|(i, entry)| {
if entry.is_none() {
Some(self.tx.as_ref().inputs[i].previous_outpoint)
} else {
None
}
})
}
pub fn clear_entries(&mut self) {
for entry in self.entries.iter_mut() {
*entry = None;
}
}
}
struct MutableTransactionVerifiableWrapper<'a, T: AsRef<Transaction>> {
inner: &'a MutableTransaction<T>,
}
impl<T: AsRef<Transaction>> VerifiableTransaction for MutableTransactionVerifiableWrapper<'_, T> {
fn tx(&self) -> &Transaction {
self.inner.tx.as_ref()
}
fn populated_input(&self, index: usize) -> (&TransactionInput, &UtxoEntry) {
(
&self.inner.tx.as_ref().inputs[index],
self.inner.entries[index].as_ref().expect("expected to be called only following full UTXO population"),
)
}
}
impl MutableTransaction {
pub fn from_tx(tx: Transaction) -> Self {
Self::new(std::sync::Arc::new(tx))
}
}
pub type SignableTransaction = MutableTransaction<Transaction>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spk_borsh() {
let spk = ScriptPublicKey::from_vec(12, vec![32; 20]);
let bin = spk.try_to_vec().unwrap();
let spk2: ScriptPublicKey = BorshDeserialize::try_from_slice(&bin).unwrap();
assert_eq!(spk, spk2);
let spk = ScriptPublicKey::from_vec(55455, vec![11; 200]);
let bin = spk.try_to_vec().unwrap();
let spk2: ScriptPublicKey = BorshDeserialize::try_from_slice(&bin).unwrap();
assert_eq!(spk, spk2);
}
}