primitives: fix TransactionDecoder::end to not panic on early calls to end
What changed, and why it matters
This commit fixes a panic (sudden crash) in the Rust Bitcoin library's transaction decoder. Previously, if a caller tried to finish decoding a Bitcoin transaction before all its data had been received, the program would crash with a panic. Now it returns a normal error instead, which is safer and easier for applications to handle gracefully.
Treat this as a low-to-moderate reliability/security hardening fix. Applications using streaming transaction decoding should upgrade to avoid unexpected panics on malformed or truncated transaction data. Review callers of `TransactionDecoder::end()` to ensure they now handle the new `EarlyEnd` error.
Security signals we found
Denial-of-service vector: untrusted or truncated transaction input could trigger a panic in decoding code
Panic-to-error conversion in parsing/decoder path
Input-validation improvement for streaming transaction deserialization
Evidence from the diff
The patch changes TransactionDecoder::end() in primitives/src/transaction.rs so that incomplete decoder states return Err(E(Inner::EarlyEnd(...))) rather than calling panic!. A new EarlyEnd(&'static str) variant is added to TransactionDecoderErrorInner, with corresponding Display and std::error::Error implementations. The Errored state still panics, preserving the existing programmer-error behavior for that case. The change converts a runtime crash path into a recoverable error path when the input stream ends prematurely.
Changed components
primitives/src/transaction.rsTransactionDecoderTransactionDecoderErrorInnerInspect captured patch +15 / −7
diff --git a/primitives/src/transaction.rs b/primitives/src/transaction.rs
index b086ab84..3a438439 100644
--- a/primitives/src/transaction.rs
+++ b/primitives/src/transaction.rs
@@ -514,15 +514,18 @@ impl Decoder for TransactionDecoder {
#[inline]
fn end(self) -> Result<Self::Output, Self::Error> {
- use TransactionDecoderState as State;
+ use {
+ TransactionDecoderError as E, TransactionDecoderErrorInner as Inner,
+ TransactionDecoderState as State,
+ };
match self.state {
- State::Version(_) => panic!("tried to end decoder in state: Version"),
- State::Inputs(..) => panic!("tried to end decoder in state: Inputs"),
- State::SegwitFlag(..) => panic!("tried to end decoder in state: SegwitFlag"),
- State::Outputs(..) => panic!("tried to end decoder in state: Outputs"),
- State::Witnesses(..) => panic!("tried to end decoder in state: Witnesses"),
- State::LockTime(..) => panic!("tried to end decoder in state: LockTime"),
+ State::Version(_) => Err(E(Inner::EarlyEnd("version"))),
+ State::Inputs(..) => Err(E(Inner::EarlyEnd("inputs"))),
+ State::SegwitFlag(..) => Err(E(Inner::EarlyEnd("segwit flag"))),
+ State::Outputs(..) => Err(E(Inner::EarlyEnd("outputs"))),
+ State::Witnesses(..) => Err(E(Inner::EarlyEnd("witnesses"))),
+ State::LockTime(..) => Err(E(Inner::EarlyEnd("locktime"))),
State::Done(tx) => Ok(tx),
State::Errored => panic!("call to end() after decoder errored"),
}
@@ -622,6 +625,9 @@ enum TransactionDecoderErrorInner {
NoWitnesses,
/// Error while decoding the `lock_time`.
LockTime(LockTimeDecoderError),
+ /// Attempt to call `end()` before the transaction was complete. Holds
+ /// a description of the current state.
+ EarlyEnd(&'static str),
}
#[cfg(feature = "alloc")]
@@ -672,6 +678,7 @@ impl fmt::Display for TransactionDecoderError {
E::Witness(ref e) => write_err!(f, "transaction decoder error"; e),
E::NoWitnesses => write!(f, "non-empty Segwit transaction with no witnesses"),
E::LockTime(ref e) => write_err!(f, "transaction decoder error"; e),
+ E::EarlyEnd(s) => write!(f, "early end of transaction (still decoding {})", s),
}
}
}
@@ -690,6 +697,7 @@ impl std::error::Error for TransactionDecoderError {
E::Witness(ref e) => Some(e),
E::NoWitnesses => None,
E::LockTime(ref e) => Some(e),
+ E::EarlyEnd(_) => None,
}
}
}
Why this scored 47/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.