Skip to content
Open
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
14 changes: 10 additions & 4 deletions bin/floresta-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ fn do_request(cmd: &Cli, client: Client) -> anyhow::Result<String> {
Methods::GetTxOutProof { txids, blockhash } => {
serde_json::to_string_pretty(&client.get_txout_proof(txids, blockhash))?
}
Methods::GetTransaction { txid, .. } => {
serde_json::to_string_pretty(&client.get_transaction(txid, Some(true))?)?
Methods::GetRawTransaction { txid, verbose } => {
serde_json::to_string_pretty(&client.get_raw_transaction(txid, verbose)?)?
}
Methods::RescanBlockchain {
start_block,
Expand Down Expand Up @@ -213,8 +213,14 @@ pub enum Methods {
},

/// Returns the transaction, assuming it is cached by our watch only wallet
#[command(name = "gettransaction")]
GetTransaction { txid: Txid, verbose: Option<bool> },
#[doc = include_str!("../../../doc/rpc/getrawtransaction.md")]
#[command(
name = "getrawtransaction",
about = "Returns raw transaction data for a given txid from wallet cache (controlled by verbosity level)",
long_about = Some(include_str!("../../../doc/rpc/getrawtransaction.md")),
disable_help_subcommand = true
)]
GetRawTransaction { txid: Txid, verbose: Option<u32> },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I dont think that Bitcoin Core will appear with 4 294 967 295 levels of verbosity


#[doc = include_str!("../../../doc/rpc/rescanblockchain.md")]
#[command(
Expand Down
101 changes: 11 additions & 90 deletions crates/floresta-node/src/json_rpc/blockchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use super::server::RpcChain;
use super::server::RpcImpl;
use crate::json_rpc::res::GetBlockRes;
use crate::json_rpc::res::RescanConfidence;
use crate::json_rpc::server::to_core_asm_string;

impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
async fn get_block_inner(&self, hash: BlockHash) -> Result<Block, JsonRpcError> {
Expand Down Expand Up @@ -327,7 +328,7 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {

/// Returns a label about the scriptPubKey type
/// (pubkey, pubkeyhash, multisig, nulldata, scripthash, witness_v0_keyhash, witness_v0_scripthash, witness_v1_taproot, anchor, nonstandard)
fn get_script_type_label(script: &Script) -> &'static str {
pub(super) fn get_script_type_label(script: &Script) -> &'static str {
if script.is_p2pk() {
return "pubkey";
}
Expand Down Expand Up @@ -367,20 +368,15 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
"nonstandard"
}

fn get_script_type_descriptor(script: &Script, address: &Option<Address>) -> String {
let get_addr_str = || {
address
.as_ref()
.expect("address should be Some")
.to_string()
};

if script.is_p2pk() {
let addr = get_addr_str();
return format!("pk({addr}");
}

/// TODO: This function is not compliant with Bitcoin Core.
/// See: <https://github.com/getfloresta/Floresta/issues/987>
pub(super) fn get_script_type_descriptor(script: &Script, address: &Option<Address>) -> String {
// Try script from the address
if let Some(addr) = address {
if script.is_p2pk() {
return format!("pk({addr}");
}

return format!("addr({addr})");
}

Expand All @@ -389,85 +385,10 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
return format!("raw({hex})");
}

if Self::is_anchor_type(script) {
let addr = get_addr_str();
return format!("addr({addr})");
}

let hex = script.to_hex_string();
format!("raw({hex})")
}

/// Parses the serialized opcodes in a [ScriptBuf] as numbers and it's hashes.
/// This differs from `ScriptBuf::to_asm_string` in that, `rust-bitcoin` will
/// show the the human representation of the opcode. It does not omit the number representations of
/// `OP_PUSHDATA_<N>` and `OP_PUSHBYTE<N>`. This method do the opposite: it not show the human
/// representation and omit the last opcodes, so it can be compliant with bitcoin-core.
/// For reference see <https://en.bitcoin.it/wiki/Script#Opcodes>
fn to_core_asm_string(script: &ScriptBuf) -> Result<String, JsonRpcError> {
let mut asm = vec![];
let bytes = script.as_bytes();
let mut i = 0usize;

// little reused helper to hex string
let to_hex_string = |r: &[u8]| r.iter().map(|b| format!("{b:02x}")).collect::<String>();

while i < bytes.len() {
let byte = bytes[i];
i += 1;

match byte {
// OP_0
0x00 => asm.push(format!("{}", 0)),
// OP_PUSHDATA_<N>: The next N bytes is data to be pushed onto the stack
0x01..=0x4b => {
let pushed_bytes = byte as usize;
let hex = to_hex_string(&bytes[i..i + pushed_bytes]);
asm.push(hex);
i += pushed_bytes;
}
// OP_PUSHBYTE1: the next byte contains the number of bytes to be pushed onto the stack.
0x4c => {
let pushed_bytes = bytes[i] as usize;
i += 1;
let hex = to_hex_string(&bytes[i..i + pushed_bytes]);
asm.push(hex);
i += pushed_bytes;
}
// OP_PUSHBYTE2: the next two bytes contain the number of bytes to be pushed onto the stack in little endian order.
0x4d => {
let pushed_bytes = u16::from_le_bytes([bytes[i], bytes[i + 1]]) as usize;
i += 2;
let hex = to_hex_string(&bytes[i..i + pushed_bytes]);
asm.push(hex);
i += pushed_bytes;
}
// OP_PUSHBYTE4: the next four bytes contain the number of bytes to be pushed onto the stack in little endian order.
0x4e => {
let pushed_bytes =
u32::from_le_bytes([bytes[i], bytes[i + 1], bytes[i + 2], bytes[i + 3]])
as usize;
i += 4;
let hex = to_hex_string(&bytes[i..i + pushed_bytes]);
asm.push(hex);
i += pushed_bytes;
}
// OP_1 to OP_16
0x51..=0x60 => {
// 0x50 is OP_RESERVED
let reserved = 0x50;
asm.push(format!("{}", byte - reserved));
}
// Any other opcode that should be pushed
another_one => {
asm.push(format!("{another_one:02x}"));
}
}
}

Ok(asm.join(" "))
}

/// gettxout: returns details about an unspent transaction output.
pub(super) fn get_tx_out(
&self,
Expand Down Expand Up @@ -499,7 +420,7 @@ impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
Err(_) => None,
};

let asm = Self::to_core_asm_string(&txout.script_pubkey)?;
let asm = to_core_asm_string(&txout.script_pubkey, false);
let script_pubkey = ScriptPubkey {
asm,
hex: txout.script_pubkey.to_hex_string(),
Expand Down
114 changes: 102 additions & 12 deletions crates/floresta-node/src/json_rpc/res.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,54 +73,144 @@ impl RescanConfidence {
}
}

/// The information returned by a get_raw_tx
#[derive(Deserialize, Serialize)]
pub struct RawTxJson {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did you tried to use corepc ?

/// Whether this tx is in our best known chain
pub in_active_chain: bool,

/// The hex-encoded tx
pub hex: String,

/// The sha256d of the serialized transaction without witness
pub txid: String,

/// The sha256d of the serialized transaction including witness
pub hash: String,

/// The size this transaction occupies on disk
pub size: u32,

/// The virtual size of this transaction, as define by the segwit soft-fork
pub vsize: u32,

/// The weight of this transaction, as defined by the segwit soft-fork
pub weight: u32,

/// This transaction's version. The current bigger version is 2
pub version: u32,

/// This transaction's locktime
pub locktime: u32,

/// A list of inputs being spent by this transaction
///
/// See [TxInJson] for more information about the contents of this
pub vin: Vec<TxInJson>,

/// A list of outputs being created by this tx
///
/// See [TxOutJson] for more information
pub vout: Vec<TxOutJson>,
pub blockhash: String,
pub confirmations: u32,
pub blocktime: u32,
pub time: u32,

#[serde(skip_serializing_if = "Option::is_none")]
/// The hash of the block that included this tx, if any
pub blockhash: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
/// How many blocks have been mined after this transaction's confirmation
/// including the block that confirms it. A zero value means this tx is unconfirmed
pub confirmations: Option<u32>,

#[serde(skip_serializing_if = "Option::is_none")]
/// The timestamp for the block confirming this tx, if confirmed
pub blocktime: Option<u32>,

#[serde(skip_serializing_if = "Option::is_none")]
/// Same as blocktime
pub time: Option<u32>,
}

/// A transaction output returned by some RPCs like gettransaction and getblock
#[derive(Deserialize, Serialize)]
pub struct TxOutJson {
pub value: u64,
/// The amount in btc locked in this UTXO
pub value: f64,

/// This utxo's index inside the transaction
pub n: u32,

#[serde(rename = "scriptPubKey")]
/// The locking script of this utxo
pub script_pub_key: ScriptPubKeyJson,
}

/// The locking script inside a txout
#[derive(Deserialize, Serialize)]
pub struct ScriptPubKeyJson {
/// A ASM representation for this script
///
/// Assembly is a high-level representation of a lower level code. Instructions
/// are turned into OP_XXXXX and data is hex-encoded.
/// E.g: OP_DUP OP_HASH160 <0000000000000000000000000000000000000000> OP_EQUALVERIFY OP_CHECKSIG
pub asm: String,

/// The hex-encoded raw script
pub hex: String,
pub req_sigs: u32,

#[serde(rename = "type")]
/// The type of this spk. E.g: PKH, SH, WSH, WPKH, TR, non-standard...
pub type_: String,
pub address: String,

#[serde(skip_serializing_if = "Option::is_none")]
/// Encode this script using one of the standard address types, if possible
pub address: Option<String>,

#[serde(rename = "desc")]
/// Inferred descriptor for the output
pub descriptor: String,
}

#[derive(Deserialize, Serialize)]
/// A transaction input returned by some rpcs, like gettransaction and getblock
#[derive(Deserialize, Serialize, Default)]
pub struct TxInJson {
pub txid: String,
pub vout: u32,
pub script_sig: ScriptSigJson,
#[serde(skip_serializing_if = "Option::is_none")]
/// The coinbase field is only set for coinbase transactions, and contains the hex-encoded coinbase script
pub coinbase: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
/// The txid that created this UTXO. Not set for coinbase transactions
pub txid: Option<String>,

#[serde(skip_serializing_if = "Option::is_none")]
/// The index of this UTXO inside the tx that created it. Not set for coinbase transactions
pub vout: Option<u32>,

#[serde(rename = "scriptSig", skip_serializing_if = "Option::is_none")]
/// Unlocking script that should solve the challenge and prove ownership over
/// that UTXO. Not set for coinbase transactions
pub script_sig: Option<ScriptSigJson>,

/// The nSequence field, used in relative and absolute lock-times
pub sequence: u32,
pub witness: Vec<String>,

#[serde(rename = "txinwitness", skip_serializing_if = "Option::is_none")]
/// A vector of witness elements for this input
pub witness: Option<Vec<String>>,
}

/// A representation for the transaction ScriptSig, returned by some rpcs
/// like gettransaction and getblock
#[derive(Deserialize, Serialize)]
pub struct ScriptSigJson {
/// A ASM representation for this scriptSig
///
/// Assembly is a high-level representation of a lower level code. Instructions
/// are turned into OP_XXXXX and data is hex-encoded.
/// E.g: OP_PUSHBYTES32 <000000000000000000000000000000000000000000000000000000000000000000>
pub asm: String,

/// The hex-encoded script sig
pub hex: String,
}

Expand Down
Loading
Loading