Skip to content

Add DecodeScriptSegwit Parsing and Model Support for decodescript RPC Segwit Field #290

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions integration_test/tests/raw_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,28 @@ fn raw_transactions__decode_script__modelled() {
}
}

#[test]
fn raw_transactions__decode_script_segwit__modelled() {
let node = Node::with_wallet(Wallet::Default, &["-txindex"]);
node.fund_wallet();
// This is a witness_v0_keyhash P2WPKH (pay-to-witness-public-key-hash) script
let addr = node.client.get_new_address(None, None).expect("getnewaddress");
let script = addr.script_pubkey();
let hex = script.to_hex_string();

let json = node.client.decode_script(&hex).expect("decodescript");
let model: Result<mtype::DecodeScriptSegwit, DecodeScriptError> = json.into_model();

let segwit = model.expect("DecodeScriptSegwit into model");

// Validate expected fields (based on known structure of P2WPKH)
assert!(segwit.asm.contains("OP_"));
assert_eq!(segwit.hex, script);
assert_eq!(segwit.type_, "witness_v0_keyhash");
assert!(segwit.addresses.iter().any(|a| a == &addr));
assert!(segwit.address.is_some());
}

// Script builder code copied from rust-bitcoin script unit tests.
fn arbitrary_p2pkh_script() -> ScriptBuf {
let pubkey_hash = <[u8; 20]>::from_hex("16e1ae70ff0fa102905d4af297f6912bda6cce19").unwrap();
Expand Down
8 changes: 4 additions & 4 deletions types/src/model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ pub use self::{
raw_transactions::{
AnalyzePsbt, AnalyzePsbtInput, AnalyzePsbtInputMissing, CombinePsbt, CombineRawTransaction,
ConvertToPsbt, CreatePsbt, CreateRawTransaction, DecodePsbt, DecodeRawTransaction,
DecodeScript, DescriptorProcessPsbt, FinalizePsbt, FundRawTransaction, GetRawTransaction,
GetRawTransactionVerbose, JoinPsbts, MempoolAcceptance, SendRawTransaction, SignFail,
SignRawTransaction, SubmitPackage, SubmitPackageTxResult, SubmitPackageTxResultFees,
TestMempoolAccept, UtxoUpdatePsbt,
DecodeScript, DecodeScriptSegwit, DescriptorProcessPsbt, FinalizePsbt, FundRawTransaction,
GetRawTransaction, GetRawTransactionVerbose, JoinPsbts, MempoolAcceptance,
SendRawTransaction, SignFail, SignRawTransaction, SubmitPackage, SubmitPackageTxResult,
SubmitPackageTxResultFees, TestMempoolAccept, UtxoUpdatePsbt,
},
util::{
CreateMultisig, DeriveAddresses, EstimateSmartFee, SignMessageWithPrivKey, ValidateAddress,
Expand Down
27 changes: 25 additions & 2 deletions types/src/model/raw_transactions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,31 @@ pub struct DecodeScript {
pub addresses: Vec<Address<NetworkUnchecked>>,
/// Address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH).
pub p2sh: Option<Address<NetworkUnchecked>>,
/// Address of the P2SH script wrapping this witness redeem script
pub p2sh_segwit: Option<String>,
/// Result of a witness output script wrapping this redeem script (not returned for types that should not be wrapped).
pub segwit: Option<DecodeScriptSegwit>,
/// Address of the P2SH script wrapping this witness redeem script.
pub p2sh_segwit: Option<Address<NetworkUnchecked>>,
}
/// Models the `segwit` field returned by the `decodescript` RPC.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DecodeScriptSegwit {
/// Disassembly of the script.
pub asm: String,
/// The raw output script bytes, hex-encoded.
pub hex: ScriptBuf,
/// The output type (e.g. nonstandard, anchor, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_scripthash, witness_v0_keyhash, witness_v1_taproot, witness_unknown).
pub type_: String,
/// Bitcoin address (only if a well-defined address exists)v22 and later only.
pub address: Option<Address<NetworkUnchecked>>,
/// The required signatures.
pub required_signatures: Option<u64>,
/// List of bitcoin addresses.
pub addresses: Vec<Address<NetworkUnchecked>>,
/// Inferred descriptor for the script. v23 and later only.
pub descriptor: Option<String>,
/// Address of the P2SH script wrapping this witness redeem script.
pub p2sh_segwit: Option<Address<NetworkUnchecked>>,
}

/// Models the result of JSON-RPC method `descriptorprocesspsbt`.
Expand Down
31 changes: 31 additions & 0 deletions types/src/v17/raw_transactions/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ pub enum DecodeScriptError {
Addresses(address::ParseError),
/// Conversion of the transaction `p2sh` field failed.
P2sh(address::ParseError),
/// Conversion of the transaction `segwit` field failed.
Segwit(DecodeScriptSegwitError),
}

impl fmt::Display for DecodeScriptError {
Expand All @@ -188,6 +190,7 @@ impl fmt::Display for DecodeScriptError {
E::Hex(ref e) => write_err!(f, "conversion of the `hex` field failed"; e),
E::Addresses(ref e) => write_err!(f, "conversion of the `addresses` field failed"; e),
E::P2sh(ref e) => write_err!(f, "conversion of the `p2sh` field failed"; e),
E::Segwit(ref e) => write_err!(f, "conversion of the `segwit` field failed"; e),
}
}
}
Expand All @@ -201,6 +204,34 @@ impl std::error::Error for DecodeScriptError {
E::Hex(ref e) => Some(e),
E::Addresses(ref e) => Some(e),
E::P2sh(ref e) => Some(e),
E::Segwit(ref e) => Some(e),
}
}
}

/// Error when converting a `DecodeScriptSegwit` type into the model type.
#[derive(Debug)]
pub enum DecodeScriptSegwitError {
/// Conversion of the transaction `addresses` field failed.
Addresses(address::ParseError),
}

impl fmt::Display for DecodeScriptSegwitError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use DecodeScriptSegwitError as E;
match *self {
E::Addresses(ref e) =>
write_err!(f, "conversion of the `addresses` field in `segwit` failed"; e),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for DecodeScriptSegwitError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use DecodeScriptSegwitError as E;
match *self {
E::Addresses(ref e) => Some(e),
}
}
}
Expand Down
35 changes: 34 additions & 1 deletion types/src/v17/raw_transactions/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use super::{
FinalizePsbt, FinalizePsbtError, FundRawTransaction, FundRawTransactionError,
GetRawTransaction, GetRawTransactionVerbose, GetRawTransactionVerboseError, MempoolAcceptance,
PsbtInput, PsbtInputError, PsbtOutput, PsbtOutputError, SendRawTransaction, SignFail,
SignFailError, SignRawTransaction, SignRawTransactionError, TestMempoolAccept,
SignFailError, SignRawTransaction, SignRawTransactionError, TestMempoolAccept,DecodeScriptSegwit,
DecodeScriptSegwitError,
};
use crate::model;
use crate::psbt::RawTransactionError;
Expand Down Expand Up @@ -310,6 +311,38 @@ impl DecodeScript {
addresses,
p2sh,
p2sh_segwit: self.p2sh_segwit,
segwit: self.segwit.map(|s| s.into_model()).transpose().map_err(E::Segwit)?,
})
}
}

impl DecodeScriptSegwit {
/// Converts version specific type to a version nonspecific, more strongly typed type.
pub fn into_model(self) -> Result<model::DecodeScriptSegwit, DecodeScriptSegwitError> {
use DecodeScriptSegwitError as E;

// Convert `Option<Vec<String>>` to `Vec<Address<NetworkUnchecked>>`
let addresses = match self.addresses {
Some(addrs) => addrs
.into_iter()
.map(|s| s.parse::<Address<_>>())
.collect::<Result<_, _>>()
.map_err(E::Addresses)?,
None => vec![],
};

let required_signatures = self.required_signatures;
let p2sh_segwit = self.p2sh_segwit;

Ok(model::DecodeScriptSegwit {
asm: self.asm,
hex: self.hex,
descriptor: None,
address:None,
type_: self.type_,
required_signatures,
addresses,
p2sh_segwit,
})
}
}
Expand Down
19 changes: 11 additions & 8 deletions types/src/v17/raw_transactions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ mod into;

use std::collections::HashMap;

use bitcoin::address::{Address, NetworkUnchecked};
use bitcoin::ScriptBuf;
use serde::{Deserialize, Serialize};

use crate::ScriptSig;
Expand All @@ -17,6 +19,7 @@ use crate::ScriptSig;
pub use self::error::{
DecodePsbtError, DecodeScriptError, FundRawTransactionError, GetRawTransactionVerboseError,
PsbtInputError, PsbtOutputError, SignFailError, SignRawTransactionError, FinalizePsbtError,
DecodeScriptSegwitError,
};
// Re-export types that appear in the public API of this module.
pub use crate::psbt::{
Expand Down Expand Up @@ -227,33 +230,33 @@ pub struct DecodeScript {
pub addresses: Option<Vec<String>>,
/// Address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH).
pub p2sh: Option<String>,
/// Segwit data (see `DecodeScriptSegwit` for explanation).
/// Result of a witness output script wrapping this redeem script (not returned for types that should not be wrapped).
pub segwit: Option<DecodeScriptSegwit>,
/// Address of the P2SH script wrapping this witness redeem script
#[serde(rename = "p2sh-segwit")]
pub p2sh_segwit: Option<String>,
pub p2sh_segwit: Option<Address<NetworkUnchecked>>,
}

/// Seemingly undocumented data returned in the `segwit` field of `DecodeScript`.
// This seems to be the same as `DecodeScript` except the `p2sh` field is called `p2sh-segwit`.
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DecodeScriptSegwit {
/// Script public key.
/// Disassembly of the script.
pub asm: String,
/// Hex encoded public key.
pub hex: String,
/// The output type.
/// The raw output script bytes, hex-encoded.
pub hex: ScriptBuf,
/// The type of the output script (e.g. witness_v0_keyhash or witness_v0_scripthash).
#[serde(rename = "type")]
pub type_: String,
/// The required signatures.
#[serde(rename = "reqSigs")]
pub required_signatures: Option<u64>,
/// List of bitcoin addresses.
pub addresses: Option<Vec<String>>,
/// Address of P2SH script wrapping this redeem script (not returned if the script is already a P2SH).
/// Address of the P2SH script wrapping this witness redeem script.
#[serde(rename = "p2sh-segwit")]
pub p2sh_segtwit: Option<String>,
pub p2sh_segwit: Option<Address<NetworkUnchecked>>,
}

/// Result of JSON-RPC method `finalizepsbt`.
Expand Down
4 changes: 3 additions & 1 deletion types/src/v22/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ pub use self::{
blockchain::GetMempoolInfo,
control::Logging,
network::{Banned, GetPeerInfo, ListBanned},
raw_transactions::{DecodeScript, DecodeScriptError},
raw_transactions::{
DecodeScript, DecodeScriptError, DecodeScriptSegwit, DecodeScriptSegwitError,
},
};
#[doc(inline)]
pub use crate::{
Expand Down
36 changes: 36 additions & 0 deletions types/src/v22/raw_transactions/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub enum DecodeScriptError {
Addresses(address::ParseError),
/// Conversion of the transaction `p2sh` field failed.
P2sh(address::ParseError),
/// Conversion of the transaction `segwit` field failed.
Segwit(DecodeScriptSegwitError),
}

impl fmt::Display for DecodeScriptError {
Expand All @@ -28,6 +30,7 @@ impl fmt::Display for DecodeScriptError {
E::Address(ref e) => write_err!(f, "conversion of the `address` field failed"; e),
E::Addresses(ref e) => write_err!(f, "conversion of the `addresses` field failed"; e),
E::P2sh(ref e) => write_err!(f, "conversion of the `p2sh` field failed"; e),
E::Segwit(ref e) => write_err!(f, "conversion of the `segwit` field failed"; e),
}
}
}
Expand All @@ -42,6 +45,39 @@ impl std::error::Error for DecodeScriptError {
E::Address(ref e) => Some(e),
E::Addresses(ref e) => Some(e),
E::P2sh(ref e) => Some(e),
E::Segwit(ref e) => Some(e),
}
}
}

/// Error when converting a `DecodeScriptSegwit` type into the model type.
#[derive(Debug)]
pub enum DecodeScriptSegwitError {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be in v17

/// Conversion of the transaction `address` field failed.
Address(address::ParseError),
/// Conversion of the transaction `addresses` field failed.
Addresses(address::ParseError),
}

impl fmt::Display for DecodeScriptSegwitError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use DecodeScriptSegwitError as E;
match *self {
E::Address(ref e) =>
write_err!(f, "conversion of the `address` field in `segwit` failed"; e),
E::Addresses(ref e) =>
write_err!(f, "conversion of the `addresses` field in `segwit` failed"; e),
}
}
}

#[cfg(feature = "std")]
impl std::error::Error for DecodeScriptSegwitError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
use DecodeScriptSegwitError as E;
match *self {
E::Address(ref e) => Some(e),
E::Addresses(ref e) => Some(e),
}
}
}
38 changes: 37 additions & 1 deletion types/src/v22/raw_transactions/into.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use bitcoin::Address;

use super::{DecodeScript, DecodeScriptError};
use super::{DecodeScript, DecodeScriptError, DecodeScriptSegwit, DecodeScriptSegwitError};
use crate::model;

impl DecodeScript {
Expand Down Expand Up @@ -32,7 +32,43 @@ impl DecodeScript {
required_signatures: self.required_signatures,
addresses,
p2sh,
segwit: self.segwit.map(|s| s.into_model()).transpose().map_err(E::Segwit)?,
p2sh_segwit: self.p2sh_segwit,
})
}
}

impl DecodeScriptSegwit {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed in v17 as well

/// Converts version specific type to a version nonspecific, more strongly typed type.
pub fn into_model(self) -> Result<model::DecodeScriptSegwit, DecodeScriptSegwitError> {
use DecodeScriptSegwitError as E;

let address = match self.address {
Some(addr) => Some(addr.parse::<Address<_>>().map_err(E::Address)?),
None => None,
};
// Convert `Option<Vec<String>>` to `Vec<Address<NetworkUnchecked>>`
let addresses = match self.addresses {
Some(addrs) => addrs
.into_iter()
.map(|s| s.parse::<Address<_>>())
.collect::<Result<_, _>>()
.map_err(E::Addresses)?,
None => vec![],
};

let required_signatures = self.required_signatures;
let p2sh_segwit = self.p2sh_segwit;

Ok(model::DecodeScriptSegwit {
asm: self.asm,
hex: self.hex,
descriptor: None,
type_: self.type_,
address,
required_signatures,
addresses,
p2sh_segwit,
})
}
}
Loading