Merge rust-bitcoin/rust-bitcoin#1609: Associate io, encode errors with psbt::Error

0ffd928a7d Carry ConsensusEncoding(encode::Error) (DanGould)
126cbb00ef Associate io::Error with psbt::Error (DanGould)

Pull request description:

  - patch 1 fix #1590
  - patch 2 addresses [this related comment](https://github.com/rust-bitcoin/rust-bitcoin/pull/1532/files#r1067812911) about associated `encode::Error`

  EDIT (by Tobin): fix #1548

  I believe this closes out PSBT refactoring and makes way for PSBTv2 in the epic 😎

  patch 1 uses match to check the error type in tests. If msrv were 1.42 we could use assert!(matches!(...)); one liners instead.

ACKs for top commit:
  apoelstra:
    ACK 0ffd928a7d
  tcharding:
    ACK 0ffd928a7d
  Kixunil:
    ACK 0ffd928a7d

Tree-SHA512: 264d6025f8979dcd1e31545fbeb2ff8d7c02f161d9aa2cc500dc07f7f4d9554fcb5ce0ff7ce473db0729560a278c71d3d41004f2613584d9b19d25103973b2ab
This commit is contained in:
Andrew Poelstra 2023-02-03 17:44:46 +00:00
commit 397d71ad7c
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
3 changed files with 33 additions and 13 deletions

View File

@ -11,6 +11,7 @@ use crate::consensus::encode;
use crate::psbt::raw; use crate::psbt::raw;
use crate::hashes; use crate::hashes;
use crate::io;
use crate::bip32::ExtendedPubKey; use crate::bip32::ExtendedPubKey;
/// Enum for marking psbt hash error. /// Enum for marking psbt hash error.
@ -22,7 +23,7 @@ pub enum PsbtHash {
Hash256, Hash256,
} }
/// Ways that a Partially Signed Transaction might fail. /// Ways that a Partially Signed Transaction might fail.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[derive(Debug)]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most /// Magic bytes for a PSBT must be the ASCII for "psbt" serialized in most
@ -73,7 +74,7 @@ pub enum Error {
/// global extended public key has inconsistent key sources /// global extended public key has inconsistent key sources
CombineInconsistentKeySources(Box<ExtendedPubKey>), CombineInconsistentKeySources(Box<ExtendedPubKey>),
/// Serialization error in bitcoin consensus-encoded structures /// Serialization error in bitcoin consensus-encoded structures
ConsensusEncoding, ConsensusEncoding(encode::Error),
/// Negative fee /// Negative fee
NegativeFee, NegativeFee,
/// Integer overflow in fee calculation /// Integer overflow in fee calculation
@ -100,6 +101,8 @@ pub enum Error {
Version(&'static str), Version(&'static str),
/// PSBT data is not consumed entirely /// PSBT data is not consumed entirely
PartialDataConsumption, PartialDataConsumption,
/// I/O error.
Io(io::Error),
} }
impl fmt::Display for Error { impl fmt::Display for Error {
@ -126,7 +129,7 @@ impl fmt::Display for Error {
write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash ) write!(f, "Preimage {:?} does not match {:?} hash {:?}", preimage, hash_type, hash )
}, },
Error::CombineInconsistentKeySources(ref s) => { write!(f, "combine conflict: {}", s) }, Error::CombineInconsistentKeySources(ref s) => { write!(f, "combine conflict: {}", s) },
Error::ConsensusEncoding => f.write_str("bitcoin consensus or BIP-174 encoding error"), Error::ConsensusEncoding(ref e) => write_err!(f, "bitcoin consensus encoding error"; e),
Error::NegativeFee => f.write_str("PSBT has a negative fee which is not allowed"), Error::NegativeFee => f.write_str("PSBT has a negative fee which is not allowed"),
Error::FeeOverflow => f.write_str("integer overflow in fee calculation"), Error::FeeOverflow => f.write_str("integer overflow in fee calculation"),
Error::InvalidPublicKey(ref e) => write_err!(f, "invalid public key"; e), Error::InvalidPublicKey(ref e) => write_err!(f, "invalid public key"; e),
@ -140,6 +143,7 @@ impl fmt::Display for Error {
Error::XPubKey(s) => write!(f, "xpub key error - {}", s), Error::XPubKey(s) => write!(f, "xpub key error - {}", s),
Error::Version(s) => write!(f, "version error {}", s), Error::Version(s) => write!(f, "version error {}", s),
Error::PartialDataConsumption => f.write_str("data not consumed entirely when explicitly deserializing"), Error::PartialDataConsumption => f.write_str("data not consumed entirely when explicitly deserializing"),
Error::Io(ref e) => write_err!(f, "I/O error"; e),
} }
} }
} }
@ -152,6 +156,8 @@ impl std::error::Error for Error {
match self { match self {
HashParse(e) => Some(e), HashParse(e) => Some(e),
ConsensusEncoding(e) => Some(e),
Io(e) => Some(e),
| InvalidMagic | InvalidMagic
| MissingUtxo | MissingUtxo
| InvalidSeparator | InvalidSeparator
@ -167,7 +173,6 @@ impl std::error::Error for Error {
| NonStandardSighashType(_) | NonStandardSighashType(_)
| InvalidPreimageHashPair{ .. } | InvalidPreimageHashPair{ .. }
| CombineInconsistentKeySources(_) | CombineInconsistentKeySources(_)
| ConsensusEncoding
| NegativeFee | NegativeFee
| FeeOverflow | FeeOverflow
| InvalidPublicKey(_) | InvalidPublicKey(_)
@ -180,7 +185,7 @@ impl std::error::Error for Error {
| Taproot(_) | Taproot(_)
| XPubKey(_) | XPubKey(_)
| Version(_) | Version(_)
| PartialDataConsumption=> None | PartialDataConsumption=> None,
} }
} }
} }
@ -193,7 +198,13 @@ impl From<hashes::Error> for Error {
} }
impl From<encode::Error> for Error { impl From<encode::Error> for Error {
fn from(_: encode::Error) -> Self { fn from(e: encode::Error) -> Self {
Error::ConsensusEncoding Error::ConsensusEncoding(e)
}
}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
} }
} }

View File

@ -1732,15 +1732,24 @@ mod tests {
// no previous output // no previous output
let mut t2 = t.clone(); let mut t2 = t.clone();
t2.inputs[0].non_witness_utxo = None; t2.inputs[0].non_witness_utxo = None;
assert_eq!(t2.fee(), Err(Error::MissingUtxo)); match t2.fee().unwrap_err() {
Error::MissingUtxo => {},
e => panic!("unexpected error: {:?}", e)
}
// negative fee // negative fee
let mut t3 = t.clone(); let mut t3 = t.clone();
t3.unsigned_tx.output[0].value = prev_output_val; t3.unsigned_tx.output[0].value = prev_output_val;
assert_eq!(t3.fee(), Err(Error::NegativeFee)); match t3.fee().unwrap_err() {
Error::NegativeFee => {},
e => panic!("unexpected error: {:?}", e)
}
// overflow // overflow
t.unsigned_tx.output[0].value = u64::max_value(); t.unsigned_tx.output[0].value = u64::max_value();
t.unsigned_tx.output[1].value = u64::max_value(); t.unsigned_tx.output[1].value = u64::max_value();
assert_eq!(t.fee(), Err(Error::FeeOverflow)); match t.fee().unwrap_err() {
Error::FeeOverflow => {},
e => panic!("unexpected error: {:?}", e)
}
} }
#[test] #[test]

View File

@ -225,7 +225,7 @@ impl Serialize for KeySource {
impl Deserialize for KeySource { impl Deserialize for KeySource {
fn deserialize(bytes: &[u8]) -> Result<Self, Error> { fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 4 { if bytes.len() < 4 {
return Err(encode::Error::from(io::Error::from(io::ErrorKind::UnexpectedEof)).into()) return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into())
} }
let fprint: Fingerprint = bytes[0..4].try_into().expect("4 is the fingerprint length"); let fprint: Fingerprint = bytes[0..4].try_into().expect("4 is the fingerprint length");
@ -319,7 +319,7 @@ impl Serialize for (XOnlyPublicKey, TapLeafHash) {
impl Deserialize for (XOnlyPublicKey, TapLeafHash) { impl Deserialize for (XOnlyPublicKey, TapLeafHash) {
fn deserialize(bytes: &[u8]) -> Result<Self, Error> { fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
if bytes.len() < 32 { if bytes.len() < 32 {
return Err(encode::Error::from(io::Error::from(io::ErrorKind::UnexpectedEof)).into()) return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into())
} }
let a: XOnlyPublicKey = Deserialize::deserialize(&bytes[..32])?; let a: XOnlyPublicKey = Deserialize::deserialize(&bytes[..32])?;
let b: TapLeafHash = Deserialize::deserialize(&bytes[32..])?; let b: TapLeafHash = Deserialize::deserialize(&bytes[32..])?;
@ -353,7 +353,7 @@ impl Serialize for (ScriptBuf, LeafVersion) {
impl Deserialize for (ScriptBuf, LeafVersion) { impl Deserialize for (ScriptBuf, LeafVersion) {
fn deserialize(bytes: &[u8]) -> Result<Self, Error> { fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
if bytes.is_empty() { if bytes.is_empty() {
return Err(encode::Error::from(io::Error::from(io::ErrorKind::UnexpectedEof)).into()) return Err(io::Error::from(io::ErrorKind::UnexpectedEof).into())
} }
// The last byte is LeafVersion. // The last byte is LeafVersion.
let script = ScriptBuf::deserialize(&bytes[..bytes.len() - 1])?; let script = ScriptBuf::deserialize(&bytes[..bytes.len() - 1])?;