Skip to content

Commit f163a85

Browse files
refactor(rpc): add maybe_async to RawTransactionRpc and centralize types
Enable async/sync flexibility for RawTransactionRpc trait with maybe_async. Remove corepc_types dependency from floresta-node, centralizing all RPC types in floresta-rpc as single source of truth.
1 parent fa9251b commit f163a85

8 files changed

Lines changed: 188 additions & 221 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/floresta-node/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ authors = ["Floresta Developers <contact@getfloresta.org>"]
1111
# All dependencies MUST be pinned precisely with `X.Y.z`
1212
axum = { workspace = true, optional = true }
1313
bitcoin = { workspace = true }
14-
corepc-types = { workspace = true }
1514
dns-lookup = { workspace = true }
1615
miniscript = { workspace = true, features = ["std"] }
1716
rcgen = { workspace = true }

crates/floresta-node/src/json_rpc/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ pub mod server;
1717
mod blockchain;
1818
mod control;
1919
mod network;
20+
mod raw_transaction;
2021
mod wallet;
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
use bitcoin::Address;
2+
use bitcoin::BlockHash;
3+
use bitcoin::ScriptBuf;
4+
use bitcoin::Transaction;
5+
use bitcoin::TxIn;
6+
use bitcoin::TxOut;
7+
use bitcoin::Txid;
8+
use bitcoin::consensus::deserialize;
9+
use bitcoin::consensus::encode::serialize_hex;
10+
use bitcoin::hashes::Hash;
11+
use bitcoin::hashes::hex::FromHex;
12+
use bitcoin::hex::DisplayHex;
13+
use floresta_rpc::rpc_interfaces::RawTransactionRpc;
14+
use floresta_rpc::rpc_types::RawTx;
15+
use floresta_rpc::rpc_types::RawTxResp;
16+
use floresta_rpc::rpc_types::ScriptPubKeyJson;
17+
use floresta_rpc::rpc_types::ScriptSigJson;
18+
use floresta_rpc::rpc_types::TxInJson;
19+
use floresta_rpc::rpc_types::TxOutJson;
20+
use floresta_watch_only::CachedTransaction;
21+
use floresta_wire::node_interface::MempoolMethods;
22+
23+
use super::server::RpcChain;
24+
use super::server::RpcImpl;
25+
use crate::json_rpc::res::jsonrpc_interface::JsonRpcError;
26+
27+
impl<Blockchain: RpcChain> RawTransactionRpc for RpcImpl<Blockchain> {
28+
type Error = JsonRpcError;
29+
30+
async fn get_raw_transaction(
31+
&self,
32+
tx_id: Txid,
33+
verbosity: Option<u32>,
34+
) -> Result<RawTxResp, JsonRpcError> {
35+
let verbosity = verbosity.unwrap_or(0);
36+
if verbosity > 1 {
37+
return Err(JsonRpcError::InvalidVerbosityLevel);
38+
}
39+
40+
let tx = self
41+
.wallet
42+
.get_transaction(&tx_id)
43+
.ok_or(JsonRpcError::TxNotFound)?;
44+
45+
if verbosity == 0 {
46+
let hex = serialize_hex(&tx.tx);
47+
return Ok(RawTxResp::Zero(hex));
48+
}
49+
50+
let raw_tx = self.make_raw_transaction(tx);
51+
52+
Ok(RawTxResp::One(Box::new(raw_tx?)))
53+
}
54+
55+
async fn send_raw_transaction(&self, tx: String) -> Result<Txid, JsonRpcError> {
56+
let tx_hex = Vec::from_hex(&tx).map_err(|_| JsonRpcError::InvalidHex)?;
57+
let tx: Transaction =
58+
deserialize(&tx_hex).map_err(|e| JsonRpcError::Decode(e.to_string()))?;
59+
60+
Ok(self
61+
.node
62+
.broadcast_transaction(tx)
63+
.await
64+
.map_err(|e| JsonRpcError::Node(e.to_string()))??)
65+
}
66+
}
67+
68+
impl<Blockchain: RpcChain> RpcImpl<Blockchain> {
69+
fn make_vin(&self, input: TxIn) -> TxInJson {
70+
let txid = serialize_hex(&input.previous_output.txid);
71+
let vout = input.previous_output.vout;
72+
let sequence = input.sequence.0;
73+
TxInJson {
74+
txid,
75+
vout,
76+
script_sig: ScriptSigJson {
77+
asm: input.script_sig.to_asm_string(),
78+
hex: input.script_sig.to_hex_string(),
79+
},
80+
witness: input
81+
.witness
82+
.iter()
83+
.map(|w| w.to_hex_string(bitcoin::hex::Case::Upper))
84+
.collect(),
85+
sequence,
86+
}
87+
}
88+
89+
fn get_script_type(script: ScriptBuf) -> Option<&'static str> {
90+
if script.is_p2pkh() {
91+
return Some("p2pkh");
92+
}
93+
if script.is_p2sh() {
94+
return Some("p2sh");
95+
}
96+
if script.is_p2wpkh() {
97+
return Some("v0_p2wpkh");
98+
}
99+
if script.is_p2wsh() {
100+
return Some("v0_p2wsh");
101+
}
102+
None
103+
}
104+
105+
fn make_vout(&self, output: TxOut, n: u32) -> TxOutJson {
106+
let value = output.value;
107+
TxOutJson {
108+
value: value.to_sat(),
109+
n,
110+
script_pub_key: ScriptPubKeyJson {
111+
asm: output.script_pubkey.to_asm_string(),
112+
hex: output.script_pubkey.to_hex_string(),
113+
req_sigs: 0, // This field is deprecated
114+
// `Address::from_script` can fail for nonstandard scripts. Bitcoin Core
115+
// omits the `address` field entirely when `ExtractDestination` fails:
116+
// https://github.com/bitcoin/bitcoin/blob/f50d53c84736f8ada8419346c4d1734d5a6686d4/src/core_io.cpp#L424
117+
address: Address::from_script(&output.script_pubkey, self.network)
118+
.ok()
119+
.map(|a| a.to_string()),
120+
type_: Self::get_script_type(output.script_pubkey)
121+
.unwrap_or("nonstandard")
122+
.to_string(),
123+
},
124+
}
125+
}
126+
127+
pub(super) fn make_raw_transaction(
128+
&self,
129+
tx: CachedTransaction,
130+
) -> Result<RawTx, JsonRpcError> {
131+
let raw_tx = tx.tx;
132+
let in_active_chain = tx.height != 0;
133+
let hex = serialize_hex(&raw_tx);
134+
let txid = serialize_hex(&raw_tx.compute_txid());
135+
let block_hash = self
136+
.chain
137+
.get_block_hash(tx.height)
138+
.unwrap_or(BlockHash::all_zeros());
139+
let tip = self.chain.get_height().map_err(|_| JsonRpcError::Chain)?;
140+
let confirmations = if in_active_chain {
141+
tip - tx.height + 1
142+
} else {
143+
0
144+
};
145+
146+
Ok(RawTx {
147+
in_active_chain,
148+
hex,
149+
txid,
150+
hash: serialize_hex(&raw_tx.compute_wtxid()),
151+
size: raw_tx.total_size() as u32,
152+
vsize: raw_tx.vsize() as u32,
153+
weight: raw_tx.weight().to_wu() as u32,
154+
version: raw_tx.version.0 as u32,
155+
locktime: raw_tx.lock_time.to_consensus_u32(),
156+
vin: raw_tx
157+
.input
158+
.iter()
159+
.map(|input| self.make_vin(input.clone()))
160+
.collect(),
161+
vout: raw_tx
162+
.output
163+
.into_iter()
164+
.enumerate()
165+
.map(|(i, output)| self.make_vout(output, i as u32))
166+
.collect(),
167+
blockhash: serialize_hex(&block_hash),
168+
confirmations,
169+
blocktime: self
170+
.chain
171+
.get_block_header(&block_hash)
172+
.map(|h| h.time)
173+
.unwrap_or(0),
174+
time: self
175+
.chain
176+
.get_block_header(&block_hash)
177+
.map(|h| h.time)
178+
.unwrap_or(0),
179+
})
180+
}
181+
}

crates/floresta-node/src/json_rpc/res.rs

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@
2121
//! [`RpcError`]: jsonrpc_interface::RpcError
2222
//! [`JsonRpcError`]: jsonrpc_interface::JsonRpcError
2323

24-
use serde::Deserialize;
25-
use serde::Serialize;
26-
2724
/// Types and methods implementing the [JSON-RPC 2.0 spec](https://www.jsonrpc.org/specification),
2825
/// tailored for floresta's RPC server. Requests using JSON-RPC 1.0 (or omitting the version
2926
/// field) are also accepted, but responses always follow the 2.0 format.
@@ -515,54 +512,3 @@ pub mod jsonrpc_interface {
515512
}
516513
}
517514
}
518-
#[derive(Deserialize, Serialize)]
519-
pub struct RawTxJson {
520-
pub in_active_chain: bool,
521-
pub hex: String,
522-
pub txid: String,
523-
pub hash: String,
524-
pub size: u32,
525-
pub vsize: u32,
526-
pub weight: u32,
527-
pub version: u32,
528-
pub locktime: u32,
529-
pub vin: Vec<TxInJson>,
530-
pub vout: Vec<TxOutJson>,
531-
pub blockhash: String,
532-
pub confirmations: u32,
533-
pub blocktime: u32,
534-
pub time: u32,
535-
}
536-
537-
#[derive(Deserialize, Serialize)]
538-
pub struct TxOutJson {
539-
pub value: u64,
540-
pub n: u32,
541-
pub script_pub_key: ScriptPubKeyJson,
542-
}
543-
544-
#[derive(Deserialize, Serialize)]
545-
pub struct ScriptPubKeyJson {
546-
pub asm: String,
547-
pub hex: String,
548-
pub req_sigs: u32,
549-
#[serde(rename = "type")]
550-
pub type_: String,
551-
#[serde(skip_serializing_if = "Option::is_none")]
552-
pub address: Option<String>,
553-
}
554-
555-
#[derive(Deserialize, Serialize)]
556-
pub struct TxInJson {
557-
pub txid: String,
558-
pub vout: u32,
559-
pub script_sig: ScriptSigJson,
560-
pub sequence: u32,
561-
pub witness: Vec<String>,
562-
}
563-
564-
#[derive(Deserialize, Serialize)]
565-
pub struct ScriptSigJson {
566-
pub asm: String,
567-
pub hex: String,
568-
}

0 commit comments

Comments
 (0)