use crate::pskt::{KeySource, PartialSigs};
use crate::utils::{combine_if_no_conflicts, Error as CombineMapErr};
use derive_builder::Builder;
use kaspa_consensus_core::{
hashing::sighash_type::{SigHashType, SIG_HASH_ALL},
tx::{TransactionId, TransactionOutpoint, UtxoEntry},
};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, marker::PhantomData, ops::Add};
#[derive(Builder, Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
#[builder(default)]
#[builder(setter(skip))]
pub struct Input {
#[builder(setter(strip_option))]
pub utxo_entry: Option<UtxoEntry>,
#[builder(setter)]
pub previous_outpoint: TransactionOutpoint,
pub sequence: Option<u64>,
#[builder(setter)]
pub min_time: Option<u64>,
pub partial_sigs: PartialSigs,
#[builder(setter)]
pub sighash_type: SigHashType,
#[serde(with = "kaspa_utils::serde_bytes_optional")]
#[builder(setter(strip_option))]
pub redeem_script: Option<Vec<u8>>,
#[builder(setter(strip_option))]
pub sig_op_count: Option<u8>,
pub bip32_derivations: BTreeMap<secp256k1::PublicKey, Option<KeySource>>,
#[serde(with = "kaspa_utils::serde_bytes_optional")]
pub final_script_sig: Option<Vec<u8>>,
#[serde(skip_serializing, default)]
pub(crate) hidden: PhantomData<()>, #[builder(setter)]
pub proprietaries: BTreeMap<String, serde_value::Value>,
#[serde(flatten)]
#[builder(setter)]
pub unknowns: BTreeMap<String, serde_value::Value>,
}
impl Default for Input {
fn default() -> Self {
Self {
utxo_entry: Default::default(),
previous_outpoint: Default::default(),
sequence: Default::default(),
min_time: Default::default(),
partial_sigs: Default::default(),
sighash_type: SIG_HASH_ALL,
redeem_script: Default::default(),
sig_op_count: Default::default(),
bip32_derivations: Default::default(),
final_script_sig: Default::default(),
hidden: Default::default(),
proprietaries: Default::default(),
unknowns: Default::default(),
}
}
}
impl Add for Input {
type Output = Result<Self, CombineError>;
fn add(mut self, rhs: Self) -> Self::Output {
if self.previous_outpoint.transaction_id != rhs.previous_outpoint.transaction_id {
return Err(CombineError::PreviousTxidMismatch {
this: self.previous_outpoint.transaction_id,
that: rhs.previous_outpoint.transaction_id,
});
}
if self.previous_outpoint.index != rhs.previous_outpoint.index {
return Err(CombineError::SpentOutputIndexMismatch {
this: self.previous_outpoint.index,
that: rhs.previous_outpoint.index,
});
}
self.utxo_entry = match (self.utxo_entry.take(), rhs.utxo_entry) {
(None, None) => None,
(Some(utxo), None) | (None, Some(utxo)) => Some(utxo),
(Some(left), Some(right)) if left == right => Some(left),
(Some(left), Some(right)) => return Err(CombineError::NotCompatibleUtxos { this: left, that: right }),
};
self.sequence = self.sequence.max(rhs.sequence);
self.min_time = self.min_time.max(rhs.min_time);
self.partial_sigs.extend(rhs.partial_sigs);
self.redeem_script = match (self.redeem_script.take(), rhs.redeem_script) {
(None, None) => None,
(Some(script), None) | (None, Some(script)) => Some(script),
(Some(script_left), Some(script_right)) if script_left == script_right => Some(script_left),
(Some(script_left), Some(script_right)) => {
return Err(CombineError::NotCompatibleRedeemScripts { this: script_left, that: script_right })
}
};
self.final_script_sig = match (self.final_script_sig.take(), rhs.final_script_sig) {
(None, None) => None,
(Some(script), None) | (None, Some(script)) => Some(script),
(Some(script_left), Some(script_right)) if script_left == script_right => Some(script_left),
(Some(script_left), Some(script_right)) => {
return Err(CombineError::NotCompatibleRedeemScripts { this: script_left, that: script_right })
}
};
self.bip32_derivations = combine_if_no_conflicts(self.bip32_derivations, rhs.bip32_derivations)?;
self.proprietaries =
combine_if_no_conflicts(self.proprietaries, rhs.proprietaries).map_err(CombineError::NotCompatibleProprietary)?;
self.unknowns = combine_if_no_conflicts(self.unknowns, rhs.unknowns).map_err(CombineError::NotCompatibleUnknownField)?;
Ok(self)
}
}
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
pub enum CombineError {
#[error("The previous txids are not the same")]
PreviousTxidMismatch {
this: TransactionId,
that: TransactionId,
},
#[error("The spent output indexes are not the same")]
SpentOutputIndexMismatch {
this: u32,
that: u32,
},
#[error("Two different redeem scripts detected")]
NotCompatibleRedeemScripts { this: Vec<u8>, that: Vec<u8> },
#[error("Two different utxos detected")]
NotCompatibleUtxos { this: UtxoEntry, that: UtxoEntry },
#[error("Two different derivations for the same key")]
NotCompatibleBip32Derivations(#[from] CombineMapErr<secp256k1::PublicKey, Option<KeySource>>),
#[error("Two different unknown field values")]
NotCompatibleUnknownField(CombineMapErr<String, serde_value::Value>),
#[error("Two different proprietary values")]
NotCompatibleProprietary(CombineMapErr<String, serde_value::Value>),
}