Remove TransactionU16LenLimited
What changed, and why it matters
This commit removes a wrapper that limited individual Bitcoin transactions to 65,535 bytes. The old limit was too generous in some places and too strict in others: it did not account for other fields in the same message, so a transaction just under the limit could still make the overall wire message too large. The patch moves the size check into the context of the enclosing message (TxAddInput) and lets the transaction field hold a normal Bitcoin transaction. It is a correctness/refactoring change for the Lightning protocol implementation, not a clear-cut remote exploit.
Review whether `LN_MAX_MSG_LEN` enforcement is also needed when receiving or forwarding `TxAddInput` from a peer, and confirm that all call sites constructing `TxAddInput` now validate the total message size. Otherwise, no urgent action is required; treat as a normal correctness improvement.
Security signals we found
Removed a coarse per-field length limit that did not reflect the real wire-level constraint
Added context-aware length check before constructing TxAddInput messages
Changed deserialization to use FixedLengthReader bounded by the declared u16 length
Potential concern: deserialization no longer rejects a transaction whose own serialized length exceeds u16::MAX, because the length field itself is u16; however, any such value cannot be encoded on the wire
Potential concern: the new check is only applied to user-supplied funding inputs in channel.rs; received peer messages are parsed but not re-checked against LN_MAX_MSG_LEN in this diff
Evidence from the diff
The TransactionU16LenLimited type enforced transaction.serialized_length() <= u16::MAX. Because a Lightning message’s total length is also capped (LN_MAX_MSG_LEN), a transaction near u16::MAX could still exceed the message limit once channel_id, serial_id, prevtx_out, sequence, and TLV fields were added. Conversely, the wrapper rejected transactions that might have fit inside a message with small other fields. The commit replaces the wrapper with raw bitcoin::Transaction in TxAddInput.prevtx, FundingNegotiationContext.our_funding_inputs, InteractiveTxConstructor.inputs_to_contribute, and related code. Serialization now writes the transaction length as u16 inline, and deserialization reads exactly that many bytes. A new check in channel.rs computes MESSAGE_TEMPLATE.serialized_length() + tx.serialized_length() and rejects funding inputs whose resulting tx_add_input message would exceed LN_MAX_MSG_LEN.
Changed components
lightning/src/util/ser.rslightning/src/ln/msgs.rs (TxAddInput serialization/deserialization)lightning/src/ln/channel.rs (funding input validation)lightning/src/ln/interactivetxs.rslightning/src/ln/dual_funding_tests.rslightning/src/ln/splicing_tests.rsInspect captured patch +110 / −124
diff --git a/lightning/src/ln/channel.rs b/lightning/src/ln/channel.rs
index 1d6dae7..60f35b9 100644
--- a/lightning/src/ln/channel.rs
+++ b/lightning/src/ln/channel.rs
@@ -72,6 +72,8 @@ use crate::ln::onion_utils::{
};
use crate::ln::script::{self, ShutdownScript};
use crate::ln::types::ChannelId;
+#[cfg(splicing)]
+use crate::ln::LN_MAX_MSG_LEN;
use crate::routing::gossip::NodeId;
use crate::sign::ecdsa::EcdsaChannelSigner;
use crate::sign::tx_builder::{SpecTxBuilder, TxBuilder};
@@ -85,9 +87,7 @@ use crate::util::config::{
use crate::util::errors::APIError;
use crate::util::logger::{Logger, Record, WithContext};
use crate::util::scid_utils::{block_from_scid, scid_from_parts};
-use crate::util::ser::{
- Readable, ReadableArgs, RequiredWrapper, TransactionU16LenLimited, Writeable, Writer,
-};
+use crate::util::ser::{Readable, ReadableArgs, RequiredWrapper, Writeable, Writer};
use alloc::collections::{btree_map, BTreeMap};
@@ -5979,7 +5979,7 @@ pub(super) struct FundingNegotiationContext {
pub shared_funding_input: Option<SharedOwnedInput>,
/// The funding inputs we will be contributing to the channel.
#[allow(dead_code)] // TODO(dual_funding): Remove once contribution to V2 channels is enabled.
- pub our_funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>,
+ pub our_funding_inputs: Vec<(TxIn, Transaction)>,
/// The change output script. This will be used if needed or -- if not set -- generated using
/// `SignerProvider::get_destination_script`.
#[allow(dead_code)] // TODO(splicing): Remove once splicing is enabled.
@@ -10671,10 +10671,26 @@ where
})?;
// Convert inputs
let mut funding_inputs = Vec::new();
- for (tx_in, tx, _w) in our_funding_inputs.into_iter() {
- let tx16 = TransactionU16LenLimited::new(tx)
- .map_err(|_e| APIError::APIMisuseError { err: format!("Too large transaction") })?;
- funding_inputs.push((tx_in, tx16));
+ for (txin, tx, _) in our_funding_inputs.into_iter() {
+ const MESSAGE_TEMPLATE: msgs::TxAddInput = msgs::TxAddInput {
+ channel_id: ChannelId([0; 32]),
+ serial_id: 0,
+ prevtx: None,
+ prevtx_out: 0,
+ sequence: 0,
+ shared_input_txid: None,
+ };
+ let message_len = MESSAGE_TEMPLATE.serialized_length() + tx.serialized_length();
+ if message_len > LN_MAX_MSG_LEN {
+ return Err(APIError::APIMisuseError {
+ err: format!(
+ "Funding input references a prevtx that is too large for tx_add_input: {}",
+ txin.previous_output,
+ ),
+ });
+ }
+
+ funding_inputs.push((txin, tx));
}
let prev_funding_input = self.funding.to_splice_funding_input();
@@ -12453,7 +12469,7 @@ where
pub fn new_outbound<ES: Deref, F: Deref, L: Deref>(
fee_estimator: &LowerBoundedFeeEstimator<F>, entropy_source: &ES, signer_provider: &SP,
counterparty_node_id: PublicKey, their_features: &InitFeatures, funding_satoshis: u64,
- funding_inputs: Vec<(TxIn, TransactionU16LenLimited)>, user_id: u128, config: &UserConfig,
+ funding_inputs: Vec<(TxIn, Transaction)>, user_id: u128, config: &UserConfig,
current_chain_height: u32, outbound_scid_alias: u64, funding_confirmation_target: ConfirmationTarget,
logger: L,
) -> Result<Self, APIError>
diff --git a/lightning/src/ln/dual_funding_tests.rs b/lightning/src/ln/dual_funding_tests.rs
index 39cf620..ab968c3 100644
--- a/lightning/src/ln/dual_funding_tests.rs
+++ b/lightning/src/ln/dual_funding_tests.rs
@@ -23,7 +23,6 @@ use {
crate::ln::msgs::{CommitmentSigned, TxAddInput, TxAddOutput, TxComplete, TxSignatures},
crate::ln::types::ChannelId,
crate::prelude::*,
- crate::util::ser::TransactionU16LenLimited,
crate::util::test_utils,
bitcoin::Witness,
};
@@ -51,7 +50,7 @@ fn do_test_v2_channel_establishment(session: V2ChannelEstablishmentTestSession)
&[session.initiator_input_value_satoshis],
)
.into_iter()
- .map(|(txin, tx, _)| (txin, TransactionU16LenLimited::new(tx).unwrap()))
+ .map(|(txin, tx, _)| (txin, tx))
.collect();
// Alice creates a dual-funded channel as initiator.
@@ -94,7 +93,7 @@ fn do_test_v2_channel_establishment(session: V2ChannelEstablishmentTestSession)
sequence: initiator_funding_inputs[0].0.sequence.0,
shared_input_txid: None,
};
- let input_value = tx_add_input_msg.prevtx.as_ref().unwrap().as_transaction().output
+ let input_value = tx_add_input_msg.prevtx.as_ref().unwrap().output
[tx_add_input_msg.prevtx_out as usize]
.value;
assert_eq!(input_value.to_sat(), session.initiator_input_value_satoshis);
diff --git a/lightning/src/ln/interactivetxs.rs b/lightning/src/ln/interactivetxs.rs
index 9853528..9fdd35e 100644
--- a/lightning/src/ln/interactivetxs.rs
+++ b/lightning/src/ln/interactivetxs.rs
@@ -32,7 +32,6 @@ use crate::ln::msgs;
use crate::ln::msgs::{MessageSendEvent, SerialId, TxSignatures};
use crate::ln::types::ChannelId;
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;
@@ -869,10 +868,9 @@ impl NegotiationContext {
return Err(AbortReason::UnexpectedFundingInput);
}
} else if let Some(prevtx) = &msg.prevtx {
- let transaction = prevtx.as_transaction();
- let txid = transaction.compute_txid();
+ let txid = prevtx.compute_txid();
- if let Some(tx_out) = transaction.output.get(msg.prevtx_out as usize) {
+ if let Some(tx_out) = prevtx.output.get(msg.prevtx_out as usize) {
if !tx_out.script_pubkey.is_witness_program() {
// The receiving node:
// - MUST fail the negotiation if:
@@ -1053,14 +1051,9 @@ impl NegotiationContext {
return Err(AbortReason::UnexpectedFundingInput);
}
} else if let Some(prevtx) = &msg.prevtx {
- let prev_txid = prevtx.as_transaction().compute_txid();
+ let prev_txid = prevtx.compute_txid();
let prev_outpoint = OutPoint { txid: prev_txid, vout: msg.prevtx_out };
- let prev_output = prevtx
- .as_transaction()
- .output
- .get(vout)
- .ok_or(AbortReason::PrevTxOutInvalid)?
- .clone();
+ let prev_output = prevtx.output.get(vout).ok_or(AbortReason::PrevTxOutInvalid)?.clone();
let txin = TxIn {
previous_output: prev_outpoint,
sequence: Sequence(msg.sequence),
@@ -1441,7 +1434,7 @@ impl_writeable_tlv_based_enum!(AddingRole,
#[derive(Clone, Debug, Eq, PartialEq)]
struct SingleOwnedInput {
input: TxIn,
- prev_tx: TransactionU16LenLimited,
+ prev_tx: Transaction,
prev_output: TxOut,
}
@@ -1843,7 +1836,7 @@ where
pub feerate_sat_per_kw: u32,
pub is_initiator: bool,
pub funding_tx_locktime: AbsoluteLockTime,
- pub inputs_to_contribute: Vec<(TxIn, TransactionU16LenLimited)>,
+ pub inputs_to_contribute: Vec<(TxIn, Transaction)>,
pub shared_funding_input: Option<SharedOwnedInput>,
pub shared_funding_output: SharedOwnedOutput,
pub outputs_to_contribute: Vec<TxOut>,
@@ -1885,7 +1878,7 @@ impl InteractiveTxConstructor {
// Check for the existence of prevouts'
for (txin, tx) in inputs_to_contribute.iter() {
let vout = txin.previous_output.vout as usize;
- if tx.as_transaction().output.get(vout).is_none() {
+ if tx.output.get(vout).is_none() {
return Err(AbortReason::PrevTxOutInvalid);
}
}
@@ -1894,7 +1887,7 @@ impl InteractiveTxConstructor {
.map(|(txin, tx)| {
let serial_id = generate_holder_serial_id(entropy_source, is_initiator);
let vout = txin.previous_output.vout as usize;
- let prev_output = tx.as_transaction().output.get(vout).unwrap().clone(); // checked above
+ let prev_output = tx.output.get(vout).unwrap().clone(); // checked above
let input =
InputOwned::Single(SingleOwnedInput { input: txin, prev_tx: tx, prev_output });
(serial_id, input)
@@ -2083,12 +2076,11 @@ pub(super) fn calculate_change_output_value(
let mut total_input_satoshis = 0u64;
let mut our_funding_inputs_weight = 0u64;
for (txin, tx) in context.our_funding_inputs.iter() {
- let txid = tx.as_transaction().compute_txid();
+ let txid = tx.compute_txid();
if txin.previous_output.txid != txid {
return Err(AbortReason::PrevTxOutInvalid);
}
let output = tx
- .as_transaction()
.output
.get(txin.previous_output.vout as usize)
.ok_or(AbortReason::PrevTxOutInvalid)?;
@@ -2145,7 +2137,6 @@ mod tests {
use crate::ln::types::ChannelId;
use crate::sign::EntropySource;
use crate::util::atomic_counter::AtomicCounter;
- use crate::util::ser::TransactionU16LenLimited;
use bitcoin::absolute::LockTime as AbsoluteLockTime;
use bitcoin::amount::Amount;
use bitcoin::hashes::Hash;
@@ -2211,12 +2202,12 @@ mod tests {
struct TestSession {
description: &'static str,
- inputs_a: Vec<(TxIn, TransactionU16LenLimited)>,
+ inputs_a: Vec<(TxIn, Transaction)>,
a_shared_input: Option<(OutPoint, TxOut, u64)>,
/// The funding output, with the value contributed
shared_output_a: (TxOut, u64),
outputs_a: Vec<TxOut>,
- inputs_b: Vec<(TxIn, TransactionU16LenLimited)>,
+ inputs_b: Vec<(TxIn, Transaction)>,
b_shared_input: Option<(OutPoint, TxOut, u64)>,
/// The funding output, with the value contributed
shared_output_b: (TxOut, u64),
@@ -2482,7 +2473,7 @@ mod tests {
}
}
- fn generate_inputs(outputs: &[TestOutput]) -> Vec<(TxIn, TransactionU16LenLimited)> {
+ fn generate_inputs(outputs: &[TestOutput]) -> Vec<(TxIn, Transaction)> {
let tx = generate_tx(outputs);
let txid = tx.compute_txid();
tx.output
@@ -2495,7 +2486,7 @@ mod tests {
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
witness: Default::default(),
};
- (txin, TransactionU16LenLimited::new(tx.clone()).unwrap())
+ (txin, tx.clone())
})
.collect()
}
@@ -2543,12 +2534,12 @@ mod tests {
(generate_txout(&TestOutput::P2WSH(value)), local_value)
}
- fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, TransactionU16LenLimited)> {
+ fn generate_fixed_number_of_inputs(count: u16) -> Vec<(TxIn, Transaction)> {
// Generate transactions with a total `count` number of outputs such that no transaction has a
// serialized length greater than u16::MAX.
let max_outputs_per_prevtx = 1_500;
let mut remaining = count;
- let mut inputs: Vec<(TxIn, TransactionU16LenLimited)> = Vec::with_capacity(count as usize);
+ let mut inputs: Vec<(TxIn, Transaction)> = Vec::with_capacity(count as usize);
while remaining > 0 {
let tx_output_count = remaining.min(max_outputs_per_prevtx);
@@ -2561,7 +2552,7 @@ mod tests {
);
let txid = tx.compute_txid();
- let mut temp: Vec<(TxIn, TransactionU16LenLimited)> = tx
+ let mut temp: Vec<(TxIn, Transaction)> = tx
.output
.iter()
.enumerate()
@@ -2572,7 +2563,7 @@ mod tests {
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
witness: Default::default(),
};
- (input, TransactionU16LenLimited::new(tx.clone()).unwrap())
+ (input, tx.clone())
})
.collect();
@@ -2783,10 +2774,9 @@ mod tests {
expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeA)),
});
- let tx =
- TransactionU16LenLimited::new(generate_tx(&[TestOutput::P2WPKH(1_000_000)])).unwrap();
+ let tx = generate_tx(&[TestOutput::P2WPKH(1_000_000)]);
let invalid_sequence_input = TxIn {
- previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 },
+ previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
..Default::default()
};
do_test_interactive_tx_constructor(TestSession {
@@ -2802,7 +2792,7 @@ mod tests {
expect_error: Some((AbortReason::IncorrectInputSequenceValue, ErrorCulprit::NodeA)),
});
let duplicate_input = TxIn {
- previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 },
+ previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..Default::default()
};
@@ -2820,7 +2810,7 @@ mod tests {
});
// Non-initiator uses same prevout as initiator.
let duplicate_input = TxIn {
- previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 },
+ previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..Default::default()
};
@@ -2837,7 +2827,7 @@ mod tests {
expect_error: Some((AbortReason::PrevTxOutInvalid, ErrorCulprit::NodeA)),
});
let duplicate_input = TxIn {
- previous_output: OutPoint { txid: tx.as_transaction().compute_txid(), vout: 0 },
+ previous_output: OutPoint { txid: tx.compute_txid(), vout: 0 },
sequence: Sequence::ENABLE_RBF_NO_LOCKTIME,
..Default::default()
};
@@ -3170,9 +3160,9 @@ mod tests {
sequence: Sequence::ZERO,
witness: Witness::new(),
};
- (txin, TransactionU16LenLimited::new(tx).unwrap())
+ (txin, tx)
})
- .collect::<Vec<(TxIn, TransactionU16LenLimited)>>();
+ .collect::<Vec<(TxIn, Transaction)>>();
let our_contributed = 110_000;
let txout = TxOut { value: Amount::from_sat(10_000), script_pubkey: ScriptBuf::new() };
let outputs = vec![txout];
diff --git a/lightning/src/ln/msgs.rs b/lightning/src/ln/msgs.rs
index e0219a5..71f73e0 100644
--- a/lightning/src/ln/msgs.rs
+++ b/lightning/src/ln/msgs.rs
@@ -29,7 +29,7 @@ use bitcoin::hash_types::Txid;
use bitcoin::script::ScriptBuf;
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::PublicKey;
-use bitcoin::{secp256k1, Witness};
+use bitcoin::{secp256k1, Transaction, Witness};
use crate::blinded_path::payment::{
BlindedPaymentTlvs, ForwardTlvs, ReceiveTlvs, UnauthenticatedReceiveTlvs,
@@ -63,8 +63,7 @@ use crate::util::base32;
use crate::util::logger;
use crate::util::ser::{
BigSize, FixedLengthReader, HighZeroBytesDroppedBigSize, Hostname, LengthLimitedRead,
- LengthReadable, LengthReadableArgs, Readable, ReadableArgs, TransactionU16LenLimited,
- WithoutLength, Writeable, Writer,
+ LengthReadable, LengthReadableArgs, Readable, ReadableArgs, WithoutLength, Writeable, Writer,
};
use crate::routing::gossip::{NodeAlias, NodeId};
@@ -524,7 +523,7 @@ pub struct TxAddInput {
pub serial_id: SerialId,
/// Serialized transaction that contains the output this input spends to verify that it is
/// non-malleable. Omitted for shared input.
- pub prevtx: Option<TransactionU16LenLimited>,
+ pub prevtx: Option<Transaction>,
/// The index of the output being spent
pub prevtx_out: u32,
/// The sequence number of this input
@@ -2738,16 +2737,58 @@ impl_writeable_msg!(SpliceLocked, {
splice_txid,
}, {});
-impl_writeable_msg!(TxAddInput, {
- channel_id,
- serial_id,
- prevtx,
- prevtx_out,
- sequence,
-}, {
- (0, shared_input_txid, option), // `funding_txid`
-});
+impl Writeable for TxAddInput {
+ fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
+ self.channel_id.write(w)?;
+ self.serial_id.write(w)?;
+
+ match &self.prevtx {
+ Some(tx) => {
+ (tx.serialized_length() as u16).write(w)?;
+ tx.write(w)?;
+ },
+ None => 0u16.write(w)?,
+ }
+
+ self.prevtx_out.write(w)?;
+ self.sequence.write(w)?;
+
+ encode_tlv_stream!(w, {
+ (0, self.shared_input_txid, option),
+ });
+ Ok(())
+ }
+}
+
+impl LengthReadable for TxAddInput {
+ fn read_from_fixed_length_buffer<R: LengthLimitedRead>(r: &mut R) -> Result<Self, DecodeError> {
+ let channel_id: ChannelId = Readable::read(r)?;
+ let serial_id: SerialId = Readable::read(r)?;
+
+ let prevtx_len: u16 = Readable::read(r)?;
+ let prevtx = if prevtx_len > 0 {
+ let mut tx_reader = FixedLengthReader::new(r, prevtx_len as u64);
+ let tx: Transaction = Readable::read(&mut tx_reader)?;
+ if tx_reader.bytes_remain() {
+ return Err(DecodeError::BadLengthDescriptor);
+ }
+
+ Some(tx)
+ } else {
+ None
+ };
+
+ let prevtx_out: u32 = Readable::read(r)?;
+ let sequence: u32 = Readable::read(r)?;
+ let mut shared_input_txid: Option<Txid> = None;
+ decode_tlv_stream!(r, {
+ (0, shared_input_txid, option),
+ });
+
+ Ok(TxAddInput { channel_id, serial_id, prevtx, prevtx_out, sequence, shared_input_txid })
+ }
+}
impl_writeable_msg!(TxAddOutput, {
channel_id,
serial_id,
@@ -4224,10 +4265,7 @@ mod tests {
ChannelFeatures, ChannelTypeFeatures, InitFeatures, NodeFeatures,
};
use crate::types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
- use crate::util::ser::{
- BigSize, Hostname, LengthReadable, Readable, ReadableArgs, TransactionU16LenLimited,
- Writeable,
- };
+ use crate::util::ser::{BigSize, Hostname, LengthReadable, Readable, ReadableArgs, Writeable};
use crate::util::test_utils;
use bitcoin::hex::DisplayHex;
use bitcoin::{Amount, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness};
@@ -5299,7 +5337,7 @@ mod tests {
let tx_add_input = msgs::TxAddInput {
channel_id: ChannelId::from_bytes([2; 32]),
serial_id: 4886718345,
- prevtx: Some(TransactionU16LenLimited::new(Transaction {
+ prevtx: Some(Transaction {
version: Version::TWO,
lock_time: LockTime::ZERO,
input: vec![TxIn {
@@ -5320,7 +5358,7 @@ mod tests {
script_pubkey: Address::from_str("bc1qxmk834g5marzm227dgqvynd23y2nvt2ztwcw2z").unwrap().assume_checked().script_pubkey(),
},
],
- }).unwrap()),
+ }),
prevtx_out: 305419896,
sequence: 305419896,
shared_input_txid: None,
diff --git a/lightning/src/ln/splicing_tests.rs b/lightning/src/ln/splicing_tests.rs
index 51f9f2e..c1340c8 100644
--- a/lightning/src/ln/splicing_tests.rs
+++ b/lightning/src/ln/splicing_tests.rs
@@ -154,7 +154,7 @@ fn test_v1_splice_in() {
);
} else {
// Input is the extra input
- let prevtx_value = tx_add_input_msg.prevtx.as_ref().unwrap().as_transaction().output
+ let prevtx_value = tx_add_input_msg.prevtx.as_ref().unwrap().output
[tx_add_input_msg.prevtx_out as usize]
.value
.to_sat();
@@ -182,7 +182,7 @@ fn test_v1_splice_in() {
);
if !inputs_seen_in_reverse {
// Input is the extra input
- let prevtx_value = tx_add_input2_msg.prevtx.as_ref().unwrap().as_transaction().output
+ let prevtx_value = tx_add_input2_msg.prevtx.as_ref().unwrap().output
[tx_add_input2_msg.prevtx_out as usize]
.value
.to_sat();
diff --git a/lightning/src/util/ser.rs b/lightning/src/util/ser.rs
index ac2b529..ea49e59 100644
--- a/lightning/src/util/ser.rs
+++ b/lightning/src/util/ser.rs
@@ -1676,63 +1676,6 @@ impl Readable for Duration {
}
}
-/// A wrapper for a `Transaction` which can only be constructed with [`TransactionU16LenLimited::new`]
-/// if the `Transaction`'s consensus-serialized length is <= u16::MAX.
-///
-/// Use [`TransactionU16LenLimited::into_transaction`] to convert into the contained `Transaction`.
-#[derive(Clone, Debug, Hash, PartialEq, Eq)]
-pub struct TransactionU16LenLimited(Transaction);
-
-impl TransactionU16LenLimited {
- /// Constructs a new `TransactionU16LenLimited` from a `Transaction` only if it's consensus-
- /// serialized length is <= u16::MAX.
- pub fn new(transaction: Transaction) -> Result<Self, ()> {
- if transaction.serialized_length() > (u16::MAX as usize) {
- Err(())
- } else {
- Ok(Self(transaction))
- }
- }
-
- /// Consumes this `TransactionU16LenLimited` and returns its contained `Transaction`.
- pub fn into_transaction(self) -> Transaction {
- self.0
- }
-
- /// Returns a reference to the contained `Transaction`
- pub fn as_transaction(&self) -> &Transaction {
- &self.0
- }
-}
-
-impl Writeable for Option<TransactionU16LenLimited> {
- fn write<W: Writer>(&self, w: &mut W) -> Result<(), io::Error> {
- match self {
- Some(tx) => {
- (tx.0.serialized_length() as u16).write(w)?;
- tx.0.write(w)
- },
- None => 0u16.write(w),
- }
- }
-}
-
-impl Readable for Option<TransactionU16LenLimited> {
- fn read<R: Read>(r: &mut R) -> Result<Self, DecodeError> {
- let len = <u16 as Readable>::read(r)?;
- if len == 0 {
- return Ok(None);
- }
- let mut tx_reader = FixedLengthReader::new(r, len as u64);
- let tx: Transaction = Readable::read(&mut tx_reader)?;
- if tx_reader.bytes_remain() {
- Err(DecodeError::BadLengthDescriptor)
- } else {
- Ok(Some(TransactionU16LenLimited(tx)))
- }
- }
-}
-
impl Writeable for ClaimId {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), io::Error> {
self.0.write(writer)
Why this scored 44/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.