Skip to content

Commit f71bbb9

Browse files
committed
Add features in InteractiveTxConstructor requried for V2 chan accept
1. InteractiveTxConstructorArgs is introduced to act as a single, more readable input to InteractiveTxConstructor::new(). 2. Various documentation updates.
1 parent b0dc394 commit f71bbb9

File tree

1 file changed

+145
-49
lines changed

1 file changed

+145
-49
lines changed

lightning/src/ln/interactivetxs.rs

Lines changed: 145 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,21 @@ use bitcoin::amount::Amount;
1515
use bitcoin::consensus::Encodable;
1616
use bitcoin::constants::WITNESS_SCALE_FACTOR;
1717
use bitcoin::policy::MAX_STANDARD_TX_WEIGHT;
18+
use bitcoin::secp256k1::PublicKey;
1819
use bitcoin::transaction::Version;
1920
use bitcoin::{OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Weight};
2021

2122
use crate::chain::chaininterface::fee_for_weight;
2223
use crate::events::bump_transaction::{BASE_INPUT_WEIGHT, EMPTY_SCRIPT_SIG_WEIGHT};
24+
use crate::events::MessageSendEvent;
2325
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
2426
use crate::ln::msgs;
2527
use crate::ln::msgs::SerialId;
2628
use crate::ln::types::ChannelId;
2729
use crate::sign::{EntropySource, P2TR_KEY_PATH_WITNESS_WEIGHT, P2WPKH_WITNESS_WEIGHT};
2830
use crate::util::ser::TransactionU16LenLimited;
2931

32+
use core::fmt::Display;
3033
use core::ops::Deref;
3134

3235
/// The number of received `tx_add_input` messages during a negotiation at which point the
@@ -43,7 +46,7 @@ const MAX_INPUTS_OUTPUTS_COUNT: usize = 252;
4346

4447
/// The total weight of the common fields whose fee is paid by the initiator of the interactive
4548
/// transaction construction protocol.
46-
const TX_COMMON_FIELDS_WEIGHT: u64 = (4 /* version */ + 4 /* locktime */ + 1 /* input count */ +
49+
pub(crate) const TX_COMMON_FIELDS_WEIGHT: u64 = (4 /* version */ + 4 /* locktime */ + 1 /* input count */ +
4750
1 /* output count */) * WITNESS_SCALE_FACTOR as u64 + 2 /* segwit marker + flag */;
4851

4952
// BOLT 3 - Lower bounds for input weights
@@ -108,6 +111,47 @@ pub(crate) enum AbortReason {
108111
InvalidLowFundingOutputValue,
109112
}
110113

114+
impl AbortReason {
115+
pub fn into_tx_abort_msg(self, channel_id: ChannelId) -> msgs::TxAbort {
116+
msgs::TxAbort { channel_id, data: self.to_string().into_bytes() }
117+
}
118+
}
119+
120+
impl Display for AbortReason {
121+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122+
f.write_str(match self {
123+
AbortReason::InvalidStateTransition => "State transition was invalid",
124+
AbortReason::UnexpectedCounterpartyMessage => "Unexpected message",
125+
AbortReason::ReceivedTooManyTxAddInputs => "Too many `tx_add_input`s received",
126+
AbortReason::ReceivedTooManyTxAddOutputs => "Too many `tx_add_output`s received",
127+
AbortReason::IncorrectInputSequenceValue => {
128+
"Input has a sequence value greater than 0xFFFFFFFD"
129+
},
130+
AbortReason::IncorrectSerialIdParity => "Parity for `serial_id` was incorrect",
131+
AbortReason::SerialIdUnknown => "The `serial_id` is unknown",
132+
AbortReason::DuplicateSerialId => "The `serial_id` already exists",
133+
AbortReason::PrevTxOutInvalid => "Invalid previous transaction output",
134+
AbortReason::ExceededMaximumSatsAllowed => {
135+
"Output amount exceeded total bitcoin supply"
136+
},
137+
AbortReason::ExceededNumberOfInputsOrOutputs => "Too many inputs or outputs",
138+
AbortReason::TransactionTooLarge => "Transaction weight is too large",
139+
AbortReason::BelowDustLimit => "Output amount is below the dust limit",
140+
AbortReason::InvalidOutputScript => "The output script is non-standard",
141+
AbortReason::InsufficientFees => "Insufficient fees paid",
142+
AbortReason::OutputsValueExceedsInputsValue => {
143+
"Total value of outputs exceeds total value of inputs"
144+
},
145+
AbortReason::InvalidTx => "The transaction is invalid",
146+
AbortReason::MissingFundingOutput => "No shared funding output found",
147+
AbortReason::DuplicateFundingOutput => "More than one funding output found",
148+
AbortReason::InvalidLowFundingOutputValue => {
149+
"Local part of funding output value is greater than the funding output value"
150+
},
151+
})
152+
}
153+
}
154+
111155
#[derive(Debug, Clone, PartialEq, Eq)]
112156
pub(crate) struct ConstructedTransaction {
113157
holder_is_initiator: bool,
@@ -1048,8 +1092,9 @@ impl InteractiveTxInput {
10481092
}
10491093
}
10501094

1051-
pub(crate) struct InteractiveTxConstructor {
1095+
pub(super) struct InteractiveTxConstructor {
10521096
state_machine: StateMachine,
1097+
initiator_first_message: Option<InteractiveTxMessageSend>,
10531098
channel_id: ChannelId,
10541099
inputs_to_contribute: Vec<(SerialId, TxIn, TransactionU16LenLimited)>,
10551100
outputs_to_contribute: Vec<(SerialId, OutputOwned)>,
@@ -1062,6 +1107,39 @@ pub(crate) enum InteractiveTxMessageSend {
10621107
TxComplete(msgs::TxComplete),
10631108
}
10641109

1110+
impl InteractiveTxMessageSend {
1111+
pub fn into_msg_send_event(self, counterparty_node_id: PublicKey) -> MessageSendEvent {
1112+
match self {
1113+
InteractiveTxMessageSend::TxAddInput(msg) => {
1114+
MessageSendEvent::SendTxAddInput { node_id: counterparty_node_id, msg }
1115+
},
1116+
InteractiveTxMessageSend::TxAddOutput(msg) => {
1117+
MessageSendEvent::SendTxAddOutput { node_id: counterparty_node_id, msg }
1118+
},
1119+
InteractiveTxMessageSend::TxComplete(msg) => {
1120+
MessageSendEvent::SendTxComplete { node_id: counterparty_node_id, msg }
1121+
},
1122+
}
1123+
}
1124+
}
1125+
1126+
pub(super) struct InteractiveTxMessageSendResult(
1127+
pub Result<InteractiveTxMessageSend, msgs::TxAbort>,
1128+
);
1129+
1130+
impl InteractiveTxMessageSendResult {
1131+
pub fn into_msg_send_event(self, counterparty_node_id: PublicKey) -> MessageSendEvent {
1132+
match self.0 {
1133+
Ok(interactive_tx_msg_send) => {
1134+
interactive_tx_msg_send.into_msg_send_event(counterparty_node_id)
1135+
},
1136+
Err(tx_abort_msg) => {
1137+
MessageSendEvent::SendTxAbort { node_id: counterparty_node_id, msg: tx_abort_msg }
1138+
},
1139+
}
1140+
}
1141+
}
1142+
10651143
// This macro executes a state machine transition based on a provided action.
10661144
macro_rules! do_state_transition {
10671145
($self: ident, $transition: ident, $msg: expr) => {{
@@ -1094,6 +1172,22 @@ pub(crate) enum HandleTxCompleteValue {
10941172
NegotiationComplete(ConstructedTransaction),
10951173
}
10961174

1175+
pub(super) struct HandleTxCompleteResult(pub Result<HandleTxCompleteValue, msgs::TxAbort>);
1176+
1177+
pub(super) struct InteractiveTxConstructorArgs<'a, ES: Deref>
1178+
where
1179+
ES::Target: EntropySource,
1180+
{
1181+
pub entropy_source: &'a ES,
1182+
pub channel_id: ChannelId,
1183+
pub feerate_sat_per_kw: u32,
1184+
pub is_initiator: bool,
1185+
pub funding_tx_locktime: AbsoluteLockTime,
1186+
pub inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>,
1187+
pub outputs_to_contribute: Vec<OutputOwned>,
1188+
pub expected_remote_shared_funding_output: Option<(ScriptBuf, u64)>,
1189+
}
1190+
10971191
impl InteractiveTxConstructor {
10981192
/// Instantiates a new `InteractiveTxConstructor`.
10991193
///
@@ -1103,20 +1197,24 @@ impl InteractiveTxConstructor {
11031197
/// and its (local) contribution from the shared output:
11041198
/// 0 when the whole value belongs to the remote node, or
11051199
/// positive if owned also by local.
1106-
/// Note: The local value cannot be larger that the actual shared output.
1200+
/// Note: The local value cannot be larger than the actual shared output.
11071201
///
1108-
/// A tuple is returned containing the newly instantiate `InteractiveTxConstructor` and optionally
1109-
/// an initial wrapped `Tx_` message which the holder needs to send to the counterparty.
1110-
pub fn new<ES: Deref>(
1111-
entropy_source: &ES, channel_id: ChannelId, feerate_sat_per_kw: u32, is_initiator: bool,
1112-
funding_tx_locktime: AbsoluteLockTime,
1113-
inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>,
1114-
outputs_to_contribute: Vec<OutputOwned>,
1115-
expected_remote_shared_funding_output: Option<(ScriptBuf, u64)>,
1116-
) -> Result<(Self, Option<InteractiveTxMessageSend>), AbortReason>
1202+
/// If the holder is the initiator, they need to send the first message which is a `TxAddInput`
1203+
/// message.
1204+
pub fn new<ES: Deref>(args: InteractiveTxConstructorArgs<ES>) -> Result<Self, AbortReason>
11171205
where
11181206
ES::Target: EntropySource,
11191207
{
1208+
let InteractiveTxConstructorArgs {
1209+
entropy_source,
1210+
channel_id,
1211+
feerate_sat_per_kw,
1212+
is_initiator,
1213+
funding_tx_locktime,
1214+
inputs_to_contribute,
1215+
outputs_to_contribute,
1216+
expected_remote_shared_funding_output,
1217+
} = args;
11201218
// Sanity check: There can be at most one shared output, local-added or remote-added
11211219
let mut expected_shared_funding_output: Option<(ScriptBuf, u64)> = None;
11221220
for output in &outputs_to_contribute {
@@ -1175,28 +1273,27 @@ impl InteractiveTxConstructor {
11751273
.collect();
11761274
// In the same manner and for the same rationale as the inputs above, we'll shuffle the outputs.
11771275
outputs_to_contribute.sort_unstable_by_key(|(serial_id, _)| *serial_id);
1178-
let mut constructor =
1179-
Self { state_machine, channel_id, inputs_to_contribute, outputs_to_contribute };
1180-
let message_send = if is_initiator {
1181-
match constructor.maybe_send_message() {
1182-
Ok(msg_send) => Some(msg_send),
1183-
Err(_) => {
1184-
debug_assert!(
1185-
false,
1186-
"We should always be able to start our state machine successfully"
1187-
);
1188-
None
1189-
},
1190-
}
1191-
} else {
1192-
None
1276+
let mut constructor = Self {
1277+
state_machine,
1278+
initiator_first_message: None,
1279+
channel_id,
1280+
inputs_to_contribute,
1281+
outputs_to_contribute,
11931282
};
1194-
Ok((constructor, message_send))
1283+
// We'll store the first message for the initiator.
1284+
if is_initiator {
1285+
constructor.initiator_first_message = Some(constructor.maybe_send_message()?);
1286+
}
1287+
Ok(constructor)
11951288
} else {
11961289
Err(AbortReason::MissingFundingOutput)
11971290
}
11981291
}
11991292

1293+
pub fn take_initiator_first_message(&mut self) -> Option<InteractiveTxMessageSend> {
1294+
self.initiator_first_message.take()
1295+
}
1296+
12001297
fn maybe_send_message(&mut self) -> Result<InteractiveTxMessageSend, AbortReason> {
12011298
// We first attempt to send inputs we want to add, then outputs. Once we are done sending
12021299
// them both, then we always send tx_complete.
@@ -1295,8 +1392,8 @@ mod tests {
12951392
use crate::ln::channel::TOTAL_BITCOIN_SUPPLY_SATOSHIS;
12961393
use crate::ln::interactivetxs::{
12971394
generate_holder_serial_id, AbortReason, HandleTxCompleteValue, InteractiveTxConstructor,
1298-
InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT, MAX_RECEIVED_TX_ADD_INPUT_COUNT,
1299-
MAX_RECEIVED_TX_ADD_OUTPUT_COUNT,
1395+
InteractiveTxConstructorArgs, InteractiveTxMessageSend, MAX_INPUTS_OUTPUTS_COUNT,
1396+
MAX_RECEIVED_TX_ADD_INPUT_COUNT, MAX_RECEIVED_TX_ADD_OUTPUT_COUNT,
13001397
};
13011398
use crate::ln::types::ChannelId;
13021399
use crate::sign::EntropySource;
@@ -1395,7 +1492,7 @@ mod tests {
13951492
ES::Target: EntropySource,
13961493
{
13971494
let channel_id = ChannelId(entropy_source.get_secure_random_bytes());
1398-
let tx_locktime = AbsoluteLockTime::from_height(1337).unwrap();
1495+
let funding_tx_locktime = AbsoluteLockTime::from_height(1337).unwrap();
13991496

14001497
// funding output sanity check
14011498
let shared_outputs_by_a: Vec<_> =
@@ -1448,16 +1545,16 @@ mod tests {
14481545
}
14491546
}
14501547

1451-
let (mut constructor_a, first_message_a) = match InteractiveTxConstructor::new(
1548+
let mut constructor_a = match InteractiveTxConstructor::new(InteractiveTxConstructorArgs {
14521549
entropy_source,
14531550
channel_id,
1454-
TEST_FEERATE_SATS_PER_KW,
1455-
true,
1456-
tx_locktime,
1457-
session.inputs_a,
1458-
session.outputs_a.to_vec(),
1459-
session.a_expected_remote_shared_output,
1460-
) {
1551+
feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW,
1552+
is_initiator: true,
1553+
funding_tx_locktime,
1554+
inputs_to_contribute: session.inputs_a,
1555+
outputs_to_contribute: session.outputs_a.to_vec(),
1556+
expected_remote_shared_funding_output: session.a_expected_remote_shared_output,
1557+
}) {
14611558
Ok(r) => r,
14621559
Err(abort_reason) => {
14631560
assert_eq!(
@@ -1469,16 +1566,16 @@ mod tests {
14691566
return;
14701567
},
14711568
};
1472-
let (mut constructor_b, first_message_b) = match InteractiveTxConstructor::new(
1569+
let mut constructor_b = match InteractiveTxConstructor::new(InteractiveTxConstructorArgs {
14731570
entropy_source,
14741571
channel_id,
1475-
TEST_FEERATE_SATS_PER_KW,
1476-
false,
1477-
tx_locktime,
1478-
session.inputs_b,
1479-
session.outputs_b.to_vec(),
1480-
session.b_expected_remote_shared_output,
1481-
) {
1572+
feerate_sat_per_kw: TEST_FEERATE_SATS_PER_KW,
1573+
is_initiator: false,
1574+
funding_tx_locktime,
1575+
inputs_to_contribute: session.inputs_b,
1576+
outputs_to_contribute: session.outputs_b.to_vec(),
1577+
expected_remote_shared_funding_output: session.b_expected_remote_shared_output,
1578+
}) {
14821579
Ok(r) => r,
14831580
Err(abort_reason) => {
14841581
assert_eq!(
@@ -1514,8 +1611,7 @@ mod tests {
15141611
}
15151612
};
15161613

1517-
assert!(first_message_b.is_none());
1518-
let mut message_send_a = first_message_a;
1614+
let mut message_send_a = constructor_a.take_initiator_first_message();
15191615
let mut message_send_b = None;
15201616
let mut final_tx_a = None;
15211617
let mut final_tx_b = None;

0 commit comments

Comments
 (0)