diff --git a/types/src/lib.rs b/types/src/lib.rs index 763d6ac2..0adbd6f6 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -42,6 +42,8 @@ use bitcoin::hex::{self, FromHex as _}; use bitcoin::{Amount, FeeRate, ScriptBuf, Witness}; use serde::{Deserialize, Serialize}; +use crate::error::write_err; + /// Converts an `i64` numeric type to a `u32`. /// /// The Bitcoin Core JSONRPC API has fields marked as 'numeric'. It is not obvious what Rust @@ -191,7 +193,7 @@ pub struct ScriptPubkey { pub hex: String, /// Number of required signatures - deprecated in Core v22. /// - /// Only returned before in versions prior to 22 or for version 22 onwards if + /// Only returned in versions prior to 22 or for version 22 onwards if /// config option `-deprecatedrpc=addresses` is passed. #[serde(rename = "reqSigs")] pub required_signatures: Option, @@ -202,11 +204,45 @@ pub struct ScriptPubkey { pub address: Option, /// Array of bitcoin addresses - deprecated in Core v22. /// - /// Only returned before in versions prior to 22 or for version 22 onwards if + /// Only returned in versions prior to 22 or for version 22 onwards if /// config option `-deprecatedrpc=addresses` is passed. pub addresses: Option>, } +/// Error when converting a `ScriptPubkey` type into the model type. +#[derive(Debug)] +pub enum ScriptPubkeyError { + /// Conversion of the `hex` field failed. + Hex(hex::HexToBytesError), + /// Conversion of the `address` field failed. + Address(address::ParseError), + /// Conversion of the `addresses` field failed. + Addresses(address::ParseError), +} + +impl fmt::Display for ScriptPubkeyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ScriptPubkeyError::*; + match *self { + Hex(ref e) => write_err!(f, "conversion of the `hex` field failed"; e), + Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), + Addresses(ref e) => write_err!(f, "conversion of the `addresses` field failed"; e), + } + } +} + +#[cfg(feature = "std")] +impl std::error::Error for ScriptPubkeyError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use ScriptPubkeyError::*; + match *self { + Hex(ref e) => Some(e), + Address(ref e) => Some(e), + Addresses(ref e) => Some(e), + } + } +} + impl ScriptPubkey { fn script_buf(&self) -> Result { ScriptBuf::from_hex(&self.hex) @@ -215,6 +251,32 @@ impl ScriptPubkey { fn address(&self) -> Option, address::ParseError>> { self.address.as_ref().map(|addr| addr.parse::>()) } + + /// Converts version specific type to a version nonspecific, more strongly typed type. + pub fn into_model(self) -> Result { + use ScriptPubkeyError as E; + + let script_pubkey = ScriptBuf::from_hex(&self.hex).map_err(E::Hex)?; + + let address = + self.address.map(|s| s.parse::>().map_err(E::Address)).transpose()?; + + let addresses = self + .addresses + .map(|v| { + v.into_iter() + .map(|s| s.parse::>().map_err(E::Addresses)) + .collect::, _>>() + }) + .transpose()?; + + Ok(model::ScriptPubkey { + script_pubkey, + required_signatures: self.required_signatures, + address, + addresses, + }) + } } /// Data returned by Core for a script signature. diff --git a/types/src/model/blockchain.rs b/types/src/model/blockchain.rs index 382e08ca..2ab3500f 100644 --- a/types/src/model/blockchain.rs +++ b/types/src/model/blockchain.rs @@ -15,7 +15,7 @@ use bitcoin::{ }; use serde::{Deserialize, Serialize}; -use crate::ScriptPubkey; +use super::ScriptPubkey; /// Models the result of JSON-RPC method `dumptxoutset`. #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] diff --git a/types/src/model/mod.rs b/types/src/model/mod.rs index 648ce2e7..c414822b 100644 --- a/types/src/model/mod.rs +++ b/types/src/model/mod.rs @@ -18,6 +18,10 @@ mod util; mod wallet; mod zmq; +use bitcoin::address::NetworkUnchecked; +use bitcoin::{Address, ScriptBuf}; +use serde::{Deserialize, Serialize}; + #[doc(inline)] pub use self::{ blockchain::{ @@ -65,3 +69,26 @@ pub use self::{ UnloadWallet, WalletCreateFundedPsbt, WalletDisplayAddress, WalletProcessPsbt, }, }; + +/// Models the data returned by Core for a scriptPubKey. +/// +/// This is used by methods in the blockchain section and in the raw transaction section (i.e raw +/// transaction and psbt methods). +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +#[cfg_attr(feature = "serde-deny-unknown-fields", serde(deny_unknown_fields))] +pub struct ScriptPubkey { + /// The script_pubkey parsed from hex. + pub script_pubkey: ScriptBuf, + /// Number of required signatures - deprecated in Core v22. + /// + /// Only returned in versions prior to 22 or for version 22 onwards if + /// config option `-deprecatedrpc=addresses` is passed. + pub required_signatures: Option, + /// Bitcoin address (only if a well-defined address exists). + pub address: Option>, + /// Array of bitcoin addresses - deprecated in Core v22. + /// + /// Only returned in versions prior to 22 or for version 22 onwards if + /// config option `-deprecatedrpc=addresses` is passed. + pub addresses: Option>>, +} diff --git a/types/src/v29/blockchain/error.rs b/types/src/v29/blockchain/error.rs index 0ee19f62..a237a3e7 100644 --- a/types/src/v29/blockchain/error.rs +++ b/types/src/v29/blockchain/error.rs @@ -8,7 +8,7 @@ use bitcoin::hex::HexToBytesError; use bitcoin::{address, amount, hex, network}; use crate::error::write_err; -use crate::NumericError; +use crate::{NumericError, ScriptPubkeyError}; /// Error when converting a `GetBlockVerboseOne` type into the model type. #[derive(Debug)] @@ -295,6 +295,10 @@ pub enum GetDescriptorActivityError { /// We wrap the inner error to provide context. This might not be strictly necessary /// if the inner errors are distinct enough, but can be helpful. ActivityEntry(Box), // Use Box to avoid recursive type size issues + /// Conversion of the `prevout_spk` field failed. + PrevoutSpk(ScriptPubkeyError), + /// Conversion of the `output_spk` field failed. + OutputSpk(ScriptPubkeyError), } impl fmt::Display for GetDescriptorActivityError { @@ -308,6 +312,8 @@ impl fmt::Display for GetDescriptorActivityError { Script(ref e) => write_err!(f, "conversion of the script `hex` field failed"; e), Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e), ActivityEntry(ref e) => write_err!(f, "conversion of an activity entry failed"; e), + PrevoutSpk(ref e) => write_err!(f, "conversion of the `prevout_spk` field failed"; e), + OutputSpk(ref e) => write_err!(f, "conversion of the `output_spk` field failed"; e), } } } @@ -324,6 +330,8 @@ impl std::error::Error for GetDescriptorActivityError { Script(ref e) => Some(e), Address(ref e) => Some(e), ActivityEntry(ref e) => Some(&**e), // Deref the Box to get the inner error + PrevoutSpk(ref e) => Some(e), + OutputSpk(ref e) => Some(e), } } } diff --git a/types/src/v29/blockchain/into.rs b/types/src/v29/blockchain/into.rs index a2f73cf8..8f95ed92 100644 --- a/types/src/v29/blockchain/into.rs +++ b/types/src/v29/blockchain/into.rs @@ -225,6 +225,7 @@ impl GetDescriptorActivity { spend.height.map(|h| crate::to_u32(h, "height")).transpose()?; let spend_txid = Txid::from_str(&spend.spend_txid).map_err(E::Hash)?; let prevout_txid = Txid::from_str(&spend.prevout_txid).map_err(E::Hash)?; + let prevout_spk = spend.prevout_spk.into_model().map_err(E::PrevoutSpk)?; Ok(model::ActivityEntry::Spend(model::SpendActivity { amount, @@ -234,7 +235,7 @@ impl GetDescriptorActivity { spend_vout: spend.spend_vout, prevout_txid, prevout_vout: spend.prevout_vout, - prevout_spk: spend.prevout_spk, + prevout_spk, })) } ActivityEntry::Receive(receive) => { @@ -247,6 +248,7 @@ impl GetDescriptorActivity { let height = receive.height.map(|h| crate::to_u32(h, "height")).transpose()?; // Uses From let txid = Txid::from_str(&receive.txid).map_err(E::Hash)?; + let output_spk = receive.output_spk.into_model().map_err(E::OutputSpk)?; Ok(model::ActivityEntry::Receive(model::ReceiveActivity { amount, @@ -254,7 +256,7 @@ impl GetDescriptorActivity { height, txid, vout: receive.vout, - output_spk: receive.output_spk, + output_spk, })) } }