Skip to content

Commit 526cd41

Browse files
committed
Merge rust-bitcoin#5129: Followups to rust-bitcoin#4998 (fix TransactionDecoder)
27fbdf9 primitives: add unit test for incomplete transaction decoding (Andrew Poelstra) 93db1e0 primitives: fix TransactionDecoder::end to not panic on early calls to end (Andrew Poelstra) e86a56c primitives: renamed TransactionDecoderError::Transitioning to Errored (Andrew Poelstra) 681af66 primitives: remove a bunch of panics from Transaction::decoder (Andrew Poelstra) Pull request description: Refactors the `TransactionDecoder` state machine in a couple ways, eliminating some unreachable panic paths and also eliminating most of the reachable panic paths in `end()` (which should have been error returns). Fixes two of the four followup issues to rust-bitcoin#4998 -- the other two are optimizing `OutpointDecoder` which is non-essential/low-priority, and eliminating the `From` impls required by the composite `Decoder` types (which is a somewhat-hard API problem and I don't think we want to hold up the rc on it). Fixes rust-bitcoin#5125 Fixes rust-bitcoin#5122 ACKs for top commit: nyonson: ACK 27fbdf9 tcharding: ACK 27fbdf9 Tree-SHA512: 1fa7556cc8f47f578ff1293a09c76b07ae2a2de53a76eb0b52b0846ca73b203878791fd8bad350590aa9197ec173c007857059f2991515c9224d35686094f1b3
2 parents 0211ffb + 27fbdf9 commit 526cd41

File tree

1 file changed

+70
-97
lines changed

1 file changed

+70
-97
lines changed

primitives/src/transaction.rs

Lines changed: 70 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -374,51 +374,6 @@ impl Default for TransactionDecoder {
374374
fn default() -> Self { Self::new() }
375375
}
376376

377-
#[cfg(feature = "alloc")]
378-
impl TransactionDecoderState {
379-
#[track_caller]
380-
fn version_transition(&mut self) -> VersionDecoder {
381-
match mem::replace(self, TransactionDecoderState::Transitioning) {
382-
TransactionDecoderState::Version(decoder) => decoder,
383-
_ => panic!("transition called on invalid state"),
384-
}
385-
}
386-
387-
#[track_caller]
388-
fn inputs_transition(&mut self) -> VecDecoder<TxIn> {
389-
match mem::replace(self, TransactionDecoderState::Transitioning) {
390-
TransactionDecoderState::Inputs(_, _, decoder) => decoder,
391-
_ => panic!("transition called on invalid state"),
392-
}
393-
}
394-
395-
#[track_caller]
396-
fn outputs_transition(&mut self) -> (Vec<TxIn>, VecDecoder<TxOut>) {
397-
match mem::replace(self, TransactionDecoderState::Transitioning) {
398-
TransactionDecoderState::Outputs(_, inputs, _, decoder) => (inputs, decoder),
399-
_ => panic!("transition called on invalid state"),
400-
}
401-
}
402-
403-
#[track_caller]
404-
fn witness_transition(&mut self) -> (Vec<TxIn>, Vec<TxOut>, WitnessDecoder) {
405-
match mem::replace(self, TransactionDecoderState::Transitioning) {
406-
TransactionDecoderState::Witnesses(_, inputs, outputs, _, decoder) =>
407-
(inputs, outputs, decoder),
408-
_ => panic!("transition called on invalid state"),
409-
}
410-
}
411-
412-
#[track_caller]
413-
fn lock_time_transition(&mut self) -> (Vec<TxIn>, Vec<TxOut>, LockTimeDecoder) {
414-
match mem::replace(self, TransactionDecoderState::Transitioning) {
415-
TransactionDecoderState::LockTime(_, inputs, outputs, decoder) =>
416-
(inputs, outputs, decoder),
417-
_ => panic!("transition called on invalid state"),
418-
}
419-
}
420-
}
421-
422377
#[cfg(feature = "alloc")]
423378
#[allow(clippy::too_many_lines)] // TODO: Can we clean this up?
424379
impl Decoder for TransactionDecoder {
@@ -433,25 +388,50 @@ impl Decoder for TransactionDecoder {
433388
};
434389

435390
loop {
391+
// Attempt to push to the currently-active decoder and return early on success.
436392
match &mut self.state {
437393
State::Version(decoder) => {
438394
if decoder.push_bytes(bytes)? {
439395
// Still more bytes required.
440396
return Ok(true);
441397
}
442-
let decoder = self.state.version_transition();
443-
let version = decoder.end()?;
444-
self.state = State::Inputs(version, Attempt::First, VecDecoder::<TxIn>::new());
445-
}
446-
State::Inputs(version, attempt, decoder) => {
398+
},
399+
State::Inputs(_, _, decoder) => {
400+
if decoder.push_bytes(bytes)? {
401+
return Ok(true);
402+
}
403+
},
404+
State::SegwitFlag(_) => {
405+
if bytes.is_empty() {
406+
return Ok(true);
407+
}
408+
},
409+
State::Outputs(_, _, _, decoder) => {
410+
if decoder.push_bytes(bytes)? {
411+
return Ok(true);
412+
}
413+
},
414+
State::Witnesses(_, _, _, _, decoder) => {
415+
if decoder.push_bytes(bytes)? {
416+
return Ok(true);
417+
}
418+
},
419+
State::LockTime(_, _, _, decoder) => {
447420
if decoder.push_bytes(bytes)? {
448421
return Ok(true);
449422
}
450-
// Copy the state because we need mutable access to self to transition.
451-
let version = *version;
452-
let attempt = *attempt;
423+
},
424+
State::Done(..) => return Ok(false),
425+
State::Errored => panic!("call to push_bytes() after decoder errored"),
426+
}
453427

454-
let decoder = self.state.inputs_transition();
428+
// If the above failed, end the current decoder and go to the next state.
429+
match mem::replace(&mut self.state, State::Errored) {
430+
State::Version(decoder) => {
431+
let version = decoder.end()?;
432+
self.state = State::Inputs(version, Attempt::First, VecDecoder::<TxIn>::new());
433+
}
434+
State::Inputs(version, attempt, decoder) => {
455435
let inputs = decoder.end()?;
456436

457437
if Attempt::First == attempt {
@@ -475,11 +455,6 @@ impl Decoder for TransactionDecoder {
475455
}
476456
}
477457
State::SegwitFlag(version) => {
478-
if bytes.is_empty() {
479-
return Ok(true);
480-
}
481-
let version = *version;
482-
483458
let segwit_flag = bytes[0];
484459
*bytes = &bytes[1..];
485460

@@ -488,17 +463,9 @@ impl Decoder for TransactionDecoder {
488463
}
489464
self.state = State::Inputs(version, Attempt::Second, VecDecoder::<TxIn>::new());
490465
}
491-
State::Outputs(version, _, is_segwit, decoder) => {
492-
if decoder.push_bytes(bytes)? {
493-
return Ok(true);
494-
}
495-
// These types are Copy, so we can deref them but does not work for vectors.
496-
let version = *version;
497-
let is_segwit = *is_segwit;
498-
466+
State::Outputs(version, inputs, is_segwit, decoder) => {
499467
// We get the inputs vector here instead of in the pattern match because I
500468
// couldn't find another way to get it out of behind the mutable reference.
501-
let (inputs, decoder) = self.state.outputs_transition();
502469
let outputs = decoder.end()?;
503470
if is_segwit == IsSegwit::Yes {
504471
self.state = State::Witnesses(
@@ -513,14 +480,9 @@ impl Decoder for TransactionDecoder {
513480
State::LockTime(version, inputs, outputs, LockTimeDecoder::new());
514481
}
515482
}
516-
State::Witnesses(version, _, _, iteration, decoder) => {
517-
if decoder.push_bytes(bytes)? {
518-
return Ok(true);
519-
}
520-
let version = *version;
483+
State::Witnesses(version, mut inputs, outputs, iteration, decoder) => {
521484
let iteration = iteration.0;
522485

523-
let (mut inputs, outputs, decoder) = self.state.witness_transition();
524486
inputs[iteration].witness = decoder.end()?;
525487
if iteration < inputs.len() - 1 {
526488
self.state = State::Witnesses(
@@ -539,40 +501,33 @@ impl Decoder for TransactionDecoder {
539501
State::LockTime(version, inputs, outputs, LockTimeDecoder::new());
540502
}
541503
}
542-
State::LockTime(version, _, _, decoder) => {
543-
if decoder.push_bytes(bytes)? {
544-
return Ok(true);
545-
}
546-
let version = *version;
547-
548-
let (inputs, outputs, decoder) = self.state.lock_time_transition();
504+
State::LockTime(version, inputs, outputs, decoder) => {
549505
let lock_time = decoder.end()?;
550506
self.state = State::Done(Transaction { version, lock_time, inputs, outputs });
551507
return Ok(false);
552508
}
553509
State::Done(..) => return Ok(false),
554-
State::Transitioning => {
555-
panic!("use of decoder in transitioning state");
556-
}
510+
State::Errored => unreachable!("checked above"),
557511
}
558512
}
559513
}
560514

561515
#[inline]
562516
fn end(self) -> Result<Self::Output, Self::Error> {
563-
use TransactionDecoderState as State;
517+
use {
518+
TransactionDecoderError as E, TransactionDecoderErrorInner as Inner,
519+
TransactionDecoderState as State,
520+
};
564521

565522
match self.state {
566-
State::Version(_) => panic!("tried to end decoder in state: Version"),
567-
State::Inputs(..) => panic!("tried to end decoder in state: Inputs"),
568-
State::SegwitFlag(..) => panic!("tried to end decoder in state: SegwitFlag"),
569-
State::Outputs(..) => panic!("tried to end decoder in state: Outputs"),
570-
State::Witnesses(..) => panic!("tried to end decoder in state: Witnesses"),
571-
State::LockTime(..) => panic!("tried to end decoder in state: LockTime"),
523+
State::Version(_) => Err(E(Inner::EarlyEnd("version"))),
524+
State::Inputs(..) => Err(E(Inner::EarlyEnd("inputs"))),
525+
State::SegwitFlag(..) => Err(E(Inner::EarlyEnd("segwit flag"))),
526+
State::Outputs(..) => Err(E(Inner::EarlyEnd("outputs"))),
527+
State::Witnesses(..) => Err(E(Inner::EarlyEnd("witnesses"))),
528+
State::LockTime(..) => Err(E(Inner::EarlyEnd("locktime"))),
572529
State::Done(tx) => Ok(tx),
573-
State::Transitioning => {
574-
panic!("use of decoder in transitioning state");
575-
}
530+
State::Errored => panic!("call to end() after decoder errored"),
576531
}
577532
}
578533

@@ -588,7 +543,9 @@ impl Decoder for TransactionDecoder {
588543
State::Witnesses(_, _, _, _, decoder) => decoder.read_limit(),
589544
State::LockTime(_, _, _, decoder) => decoder.read_limit(),
590545
State::Done(_) => 0,
591-
State::Transitioning => panic!("use of decoder in transitioning state"),
546+
// `read_limit` is not documented to panic or return an error, so we
547+
// return a dummy value if the decoder is in an error state.
548+
State::Errored => 0,
592549
}
593550
}
594551
}
@@ -616,8 +573,9 @@ enum TransactionDecoderState {
616573
LockTime(Version, Vec<TxIn>, Vec<TxOut>, LockTimeDecoder),
617574
/// Done decoding the [`Transaction`].
618575
Done(Transaction),
619-
/// Temporary state during transitions, should never be observed.
620-
Transitioning,
576+
/// When `end()`ing a sub-decoder, encountered an error which prevented us
577+
/// from constructing the next sub-decoder.
578+
Errored,
621579
}
622580

623581
/// Boolean used to track number of times we have attempted to decode the inputs vector.
@@ -667,6 +625,9 @@ enum TransactionDecoderErrorInner {
667625
NoWitnesses,
668626
/// Error while decoding the `lock_time`.
669627
LockTime(LockTimeDecoderError),
628+
/// Attempt to call `end()` before the transaction was complete. Holds
629+
/// a description of the current state.
630+
EarlyEnd(&'static str),
670631
}
671632

672633
#[cfg(feature = "alloc")]
@@ -717,6 +678,7 @@ impl fmt::Display for TransactionDecoderError {
717678
E::Witness(ref e) => write_err!(f, "transaction decoder error"; e),
718679
E::NoWitnesses => write!(f, "non-empty Segwit transaction with no witnesses"),
719680
E::LockTime(ref e) => write_err!(f, "transaction decoder error"; e),
681+
E::EarlyEnd(s) => write!(f, "early end of transaction (still decoding {})", s),
720682
}
721683
}
722684
}
@@ -735,6 +697,7 @@ impl std::error::Error for TransactionDecoderError {
735697
E::Witness(ref e) => Some(e),
736698
E::NoWitnesses => None,
737699
E::LockTime(ref e) => Some(e),
700+
E::EarlyEnd(_) => None,
738701
}
739702
}
740703
}
@@ -2153,6 +2116,16 @@ mod tests {
21532116
decoder.push_bytes(&mut slice).unwrap();
21542117
let tx = decoder.end().unwrap();
21552118

2119+
// Attempt various truncations
2120+
for i in [1, 10, 20, 50, 100, tx_bytes.len() / 2, tx_bytes.len()] {
2121+
let mut decoder = Transaction::decoder();
2122+
let mut slice = &tx_bytes[..tx_bytes.len() - i];
2123+
// push_bytes will not fail because the data is not invalid, just truncated
2124+
decoder.push_bytes(&mut slice).unwrap();
2125+
// ...but end() will fail because we will be in some incomplete state
2126+
decoder.end().unwrap_err();
2127+
}
2128+
21562129
// All these tests aren't really needed because if they fail, the hash check at the end
21572130
// will also fail. But these will show you where the failure is so I'll leave them in.
21582131
assert_eq!(tx.version, Version::TWO);

0 commit comments

Comments
 (0)