use bitcoin::consensus::Encodable;
use bitcoin::constants::WITNESS_SCALE_FACTOR;
use bitcoin::policy::MAX_STANDARD_TX_WEIGHT;
+use bitcoin::secp256k1::PublicKey;
use bitcoin::transaction::Version;
use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Weight};
use crate::chain::chaininterface::fee_for_weight;
use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT};
+use crate::events::MessageSendEvent;
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
use crate::ln::msgs;
use crate::ln::msgs::SerialId;
use crate::sign::{EntropySource, P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT};
use crate::util::ser::TransactionU16LenLimited;
+use core::fmt::Display;
use core::ops::Deref;
/// The number of received `tx_add_input` messages during a negotiation at which point the
/// The total weight of the common fields whose fee is paid by the initiator of the interactive
/// transaction construction protocol.
-const TX_COMMON_FIELDS_WEIGHT: u64 = (4 /* version */ + 4 /* locktime */ + 1 /* input count */ +
+pub(crate) const TX_COMMON_FIELDS_WEIGHT: u64 = (4 /* version */ + 4 /* locktime */ + 1 /* input count */ +
1 /* output count */) * WITNESS_SCALE_FACTOR as u64 + 2 /* segwit marker + flag */;
// BOLT 3 - Lower bounds for input weights
InvalidLowFundingOutputValue,
}
+impl AbortReason {
+ pub fn into_tx_abort_msg(self, channel_id: ChannelId) -> msgs::TxAbort {
+ msgs::TxAbort { channel_id, data: self.to_string().into_bytes() }
+ }
+}
+
+impl Display for AbortReason {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ f.write_str(match self {
+ AbortReason::InvalidStateTransition => "State transition was invalid",
+ AbortReason::UnexpectedCounterpartyMessage => "Unexpected message",
+ AbortReason::ReceivedTooManyTxAddInputs => "Too many `tx_add_input`s received",
+ AbortReason::ReceivedTooManyTxAddOutputs => "Too many `tx_add_output`s received",
+ AbortReason::IncorrectInputSequenceValue => {
+ "Input has a sequence value greater than 0xFFFFFFFD"
+ },
+ AbortReason::IncorrectSerialIdParity => "Parity for `serial_id` was incorrect",
+ AbortReason::SerialIdUnknown => "The `serial_id` is unknown",
+ AbortReason::DuplicateSerialId => "The `serial_id` already exists",
+ AbortReason::PrevTxOutInvalid => "Invalid previous transaction output",
+ AbortReason::ExceededMaximumSatsAllowed => {
+ "Output amount exceeded total bitcoin supply"
+ },
+ AbortReason::ExceededNumberOfInputsOrOutputs => "Too many inputs or outputs",
+ AbortReason::TransactionTooLarge => "Transaction weight is too large",
+ AbortReason::BelowDustLimit => "Output amount is below the dust limit",
+ AbortReason::InvalidOutputScript => "The output script is non-standard",
+ AbortReason::InsufficientFees => "Insufficient fees paid",
+ AbortReason::OutputsValueExceedsInputsValue => {
+ "Total value of outputs exceeds total value of inputs"
+ },
+ AbortReason::InvalidTx => "The transaction is invalid",
+ AbortReason::MissingFundingOutput => "No shared funding output found",
+ AbortReason::DuplicateFundingOutput => "More than one funding output found",
+ AbortReason::InvalidLowFundingOutputValue => {
+ "Local part of funding output value is greater than the funding output value"
+ },
+ })
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct ConstructedTransaction {
holder_is_initiator: bool,
}
}
-pub(crate) struct InteractiveTxConstructor {
+pub(super) struct InteractiveTxConstructor {
state_machine: StateMachine,
+ initiator_first_message: Option<InteractiveTxMessageSend>,
channel_id: ChannelId,
inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)>,
outputs_to_contribute: Vec<(SerialId, OutputOwned)>,
TxComplete(msgs::TxComplete),
}
+impl InteractiveTxMessageSend {
+ pub fn into_msg_send_event(self, counterparty_node_id: PublicKey) -> MessageSendEvent {
+ match self {
+ InteractiveTxMessageSend::TxAddInput(msg) => {
+ MessageSendEvent::SendTxAddInput { node_id: counterparty_node_id, msg }
+ },
+ InteractiveTxMessageSend::TxAddOutput(msg) => {
+ MessageSendEvent::SendTxAddOutput { node_id: counterparty_node_id, msg }
+ },
+ InteractiveTxMessageSend::TxComplete(msg) => {
+ MessageSendEvent::SendTxComplete { node_id: counterparty_node_id, msg }
+ },
+ }
+ }
+}
+
+pub(super) struct InteractiveTxMessageSendResult(
+ pub Result<InteractiveTxMessageSend, msgs::TxAbort>,
+);
+
+impl InteractiveTxMessageSendResult {
+ pub fn into_msg_send_event(self, counterparty_node_id: PublicKey) -> MessageSendEvent {
+ match self.0 {
+ Ok(interactive_tx_msg_send) => {
+ interactive_tx_msg_send.into_msg_send_event(counterparty_node_id)
+ },
+ Err(tx_abort_msg) => {
+ MessageSendEvent::SendTxAbort { node_id: counterparty_node_id, msg: tx_abort_msg }
+ },
+ }
+ }
+}
+
// This macro executes a state machine transition based on a provided action.
macro_rules! do_state_transition {
($self: ident, $transition: ident, $msg: expr) => {{
NegotiationComplete(ConstructedTransaction),
}
+pub(super) struct HandleTxCompleteResult(pub Result<HandleTxCompleteValue, msgs::TxAbort>);
+
+pub(super) struct InteractiveTxConstructorArgs<'a, ES: Deref>
+where
+ ES::Target: EntropySource,
+{
+ pub entropy_source: &'a ES,
+ pub channel_id: ChannelId,
+ pub feerate_sat_per_kw: u32,
+ pub is_initiator: bool,
+ pub funding_tx_locktime: AbsoluteLockTime,
+ pub inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>,
+ pub outputs_to_contribute: Vec<OutputOwned>,
+ pub expected_remote_shared_funding_output: Option<(ScriptBuf, u64)>,
+}
+
impl InteractiveTxConstructor {
/// Instantiates a new `InteractiveTxConstructor`.
///
/// and its (local) contribution from the shared output:
/// 0 when the whole value belongs to the remote node, or
/// positive if owned also by local.
- /// Note: The local value cannot be larger that the actual shared output.
+ /// Note: The local value cannot be larger than the actual shared output.
///
- /// A tuple is returned containing the newly instantiate `InteractiveTxConstructor` and optionally
- /// an initial wrapped `Tx_` message which the holder needs to send to the counterparty.
- pub fn new<ES: Deref>(
- entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, is_initiator: bool,
- funding_tx_locktime: AbsoluteLockTime,
- inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>,
- outputs_to_contribute: Vec<OutputOwned>,
- expected_remote_shared_funding_output: Option<(ScriptBuf, u64)>,
- ) -> Result<(Self, Option<InteractiveTxMessageSend>), AbortReason>
+ /// If the holder is the initiator, they need to send the first message which is a `TxAddInput`
+ /// message.
+ pub fn new<ES: Deref>(args: InteractiveTxConstructorArgs<ES>) -> Result<Self, AbortReason>
where
ES::Target: EntropySource,
{
+ let InteractiveTxConstructorArgs {
+ entropy_source,
+ channel_id,
+ feerate_sat_per_kw,
+ is_initiator,
+ funding_tx_locktime,
+ inputs_to_contribute,
+ outputs_to_contribute,
+ expected_remote_shared_funding_output,
+ } = args;
// Sanity check: There can be at most one shared output, local-added or remote-added
let mut expected_shared_funding_output: Option<(ScriptBuf, u64)> = None;
for output in &outputs_to_contribute {
.collect();
// In the same manner and for the same rationale as the inputs above, we'll shuffle the outputs.
outputs_to_contribute.sort_unstable_by_key(|(serial_id, _)| *serial_id);
- let mut constructor =
- Self { state_machine, channel_id, inputs_to_contribute, outputs_to_contribute };
- let message_send = if is_initiator {
- match constructor.maybe_send_message() {
- Ok(msg_send) => Some(msg_send),
- Err(_) => {
- debug_assert!(
- false,
- "We should always be able to start our state machine successfully"
- );
- None
- },
- }
- } else {
- None
+ let mut constructor = Self {
+ state_machine,
+ initiator_first_message: None,
+ channel_id,
+ inputs_to_contribute,
+ outputs_to_contribute,
};
- Ok((constructor, message_send))
+ // We'll store the first message for the initiator.
+ if is_initiator {
+ constructor.initiator_first_message = Some(constructor.maybe_send_message()?);
+ }
+ Ok(constructor)
} else {
Err(AbortReason::MissingFundingOutput)
}
}
+ pub fn take_initiator_first_message(&mut self) -> Option<InteractiveTxMessageSend> {
+ self.initiator_first_message.take()
+ }
+
fn maybe_send_message(&mut self) -> Result<InteractiveTxMessageSend, AbortReason> {
// We first attempt to send inputs we want to add, then outputs. Once we are done sending
// them both, then we always send tx_complete.
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
use crate::ln::interactivetxs::{
generate_holder_serial_id, AbortReason, HandleTxCompleteValue, InteractiveTxConstructor,
- InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT,
- MAX_RECEIVED_TX_ADD_OUTPUT_COUNT,
+ InteractiveTxConstructorArgs, InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT,
+ MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT,
};
use crate::ln::types::ChannelId;
use crate::sign::EntropySource;
ES::Target: EntropySource,
{
let channel_id = ChannelId(entropy_source.get_secure_random_bytes());
- let tx_locktime = AbsoluteLockTime::from_height(1337).unwrap();
+ let funding_tx_locktime = AbsoluteLockTime::from_height(1337).unwrap();
// funding output sanity check
let shared_outputs_by_a: Vec<_> =
}
}
- let (mut constructor_a, first_message_a) = match InteractiveTxConstructor::new(
+ let mut constructor_a = match InteractiveTxConstructor::new(InteractiveTxConstructorArgs {
entropy_source,
channel_id,
- TEST_FEERATE_SATS_PER_KW,
- true,
- tx_locktime,
- session.inputs_a,
- session.outputs_a.to_vec(),
- session.a_expected_remote_shared_output,
- ) {
+ feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW,
+ is_initiator: true,
+ funding_tx_locktime,
+ inputs_to_contribute: session.inputs_a,
+ outputs_to_contribute: session.outputs_a.to_vec(),
+ expected_remote_shared_funding_output: session.a_expected_remote_shared_output,
+ }) {
Ok(r) => r,
Err(abort_reason) => {
assert_eq!(
return;
},
};
- let (mut constructor_b, first_message_b) = match InteractiveTxConstructor::new(
+ let mut constructor_b = match InteractiveTxConstructor::new(InteractiveTxConstructorArgs {
entropy_source,
channel_id,
- TEST_FEERATE_SATS_PER_KW,
- false,
- tx_locktime,
- session.inputs_b,
- session.outputs_b.to_vec(),
- session.b_expected_remote_shared_output,
- ) {
+ feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW,
+ is_initiator: false,
+ funding_tx_locktime,
+ inputs_to_contribute: session.inputs_b,
+ outputs_to_contribute: session.outputs_b.to_vec(),
+ expected_remote_shared_funding_output: session.b_expected_remote_shared_output,
+ }) {
Ok(r) => r,
Err(abort_reason) => {
assert_eq!(
}
};
- assert!(first_message_b.is_none());
- let mut message_send_a = first_message_a;
+ let mut message_send_a = constructor_a.take_initiator_first_message();
let mut message_send_b = None;
let mut final_tx_a = None;
let mut final_tx_b = None;