Skip to content

Commit 361607c

Browse files
committed
add get_transaction and get_transaction_info RPCs
1 parent 5796aa8 commit 361607c

File tree

11 files changed

+178
-28
lines changed

11 files changed

+178
-28
lines changed

Cargo.lock

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ members = [
1010
[workspace.package]
1111
authors = [ "Ash Manning <[email protected]>" ]
1212
edition = "2021"
13-
version = "0.7.0"
13+
version = "0.7.1"
1414

1515
[workspace.dependencies.bip300301]
1616
git = "https://github.com/Ash-L2L/bip300301.git"

app/app.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ impl App {
164164
.map(|(outpoint, txid)| {
165165
let inclusions = self.node.get_tx_inclusions(txid)?;
166166
let Some(block_hash) =
167-
inclusions.into_iter().try_find(|block_hash| {
167+
inclusions.into_keys().try_find(|block_hash| {
168168
self.node.is_descendant(*block_hash, tip)
169169
})?
170170
else {
@@ -184,7 +184,7 @@ impl App {
184184
)
185185
};
186186
let inclusions = self.node.get_tx_inclusions(txid)?;
187-
let Some(block_hash) = inclusions.into_iter().try_find(|block_hash| {
187+
let Some(block_hash) = inclusions.into_keys().try_find(|block_hash| {
188188
self.node.is_descendant(*block_hash, tip)
189189
})? else {
190190
return Ok((spent_output.inpoint, None));

app/rpc_server.rs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use plain_bitnames::{
1515
},
1616
wallet,
1717
};
18-
use plain_bitnames_app_rpc_api::RpcServer;
18+
use plain_bitnames_app_rpc_api::{RpcServer, TxInfo};
1919

2020
use crate::app::{self, App};
2121

@@ -94,6 +94,50 @@ impl RpcServer for RpcServerImpl {
9494
self.app.get_paymail(None).map_err(convert_app_err)
9595
}
9696

97+
async fn get_transaction(
98+
&self,
99+
txid: Txid,
100+
) -> RpcResult<Option<Transaction>> {
101+
self.app
102+
.node
103+
.try_get_transaction(txid)
104+
.map_err(convert_node_err)
105+
}
106+
107+
async fn get_transaction_info(
108+
&self,
109+
txid: Txid,
110+
) -> RpcResult<Option<TxInfo>> {
111+
let Some((filled_tx, txin)) = self
112+
.app
113+
.node
114+
.try_get_filled_transaction(txid)
115+
.map_err(convert_node_err)?
116+
else {
117+
return Ok(None);
118+
};
119+
let confirmations = match txin {
120+
Some(txin) => {
121+
let tip_height =
122+
self.app.node.get_tip_height().map_err(convert_node_err)?;
123+
let height = self
124+
.app
125+
.node
126+
.get_height(txin.block_hash)
127+
.map_err(convert_node_err)?;
128+
Some(tip_height - height)
129+
}
130+
None => None,
131+
};
132+
let fee_sats = filled_tx.transaction.fee().unwrap();
133+
let res = TxInfo {
134+
confirmations,
135+
fee_sats,
136+
txin,
137+
};
138+
Ok(Some(res))
139+
}
140+
97141
async fn getblockcount(&self) -> RpcResult<u32> {
98142
self.app.node.get_tip_height().map_err(convert_node_err)
99143
}

lib/archive.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{cmp::Ordering, collections::BTreeSet};
1+
use std::{cmp::Ordering, collections::BTreeMap};
22

33
use bip300301::{
44
bitcoin::{self, block::Header as BitcoinHeader, hashes::Hash},
@@ -56,9 +56,9 @@ pub struct Archive {
5656
/// All ancestors of any block should always be present
5757
total_work:
5858
Database<SerdeBincode<bitcoin::BlockHash>, SerdeBincode<bitcoin::Work>>,
59-
/// Blocks in which a tx has been included
59+
/// Blocks in which a tx has been included, and index within the block
6060
txid_to_inclusions:
61-
Database<SerdeBincode<Txid>, SerdeBincode<BTreeSet<BlockHash>>>,
61+
Database<SerdeBincode<Txid>, SerdeBincode<BTreeMap<BlockHash, u32>>>,
6262
}
6363

6464
impl Archive {
@@ -255,12 +255,12 @@ impl Archive {
255255
.ok_or(Error::NoMainHeader(block_hash))
256256
}
257257

258-
/// Get blocks in which a tx was included.
258+
/// Get blocks in which a tx was included, and tx index within each block
259259
pub fn get_tx_inclusions(
260260
&self,
261261
rotxn: &RoTxn,
262262
txid: Txid,
263-
) -> Result<BTreeSet<BlockHash>, Error> {
263+
) -> Result<BTreeMap<BlockHash, u32>, Error> {
264264
let inclusions = self
265265
.txid_to_inclusions
266266
.get(rotxn, &txid)?
@@ -289,13 +289,16 @@ impl Archive {
289289
) -> Result<(), Error> {
290290
let _header = self.get_header(rwtxn, block_hash)?;
291291
self.bodies.put(rwtxn, &block_hash, body)?;
292-
body.transactions.iter().try_for_each(|tx| {
293-
let txid = tx.txid();
294-
let mut inclusions = self.get_tx_inclusions(rwtxn, txid)?;
295-
inclusions.insert(block_hash);
296-
self.txid_to_inclusions.put(rwtxn, &txid, &inclusions)?;
297-
Ok(())
298-
})
292+
body.transactions
293+
.iter()
294+
.enumerate()
295+
.try_for_each(|(txin, tx)| {
296+
let txid = tx.txid();
297+
let mut inclusions = self.get_tx_inclusions(rwtxn, txid)?;
298+
inclusions.insert(block_hash, txin as u32);
299+
self.txid_to_inclusions.put(rwtxn, &txid, &inclusions)?;
300+
Ok(())
301+
})
299302
}
300303

301304
/// Store deposit info for a block

lib/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#![feature(let_chains)]
2+
#![feature(try_find)]
23

34
pub mod archive;
45
pub mod authorization;

lib/node.rs

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{
2-
collections::{BTreeSet, HashMap, HashSet},
2+
collections::{BTreeMap, HashMap, HashSet},
33
fmt::Debug,
44
net::SocketAddr,
55
path::Path,
@@ -35,7 +35,8 @@ use crate::{
3535
types::{
3636
Address, Authorized, AuthorizedTransaction, BitName, BitNameData,
3737
Block, BlockHash, Body, FilledOutput, FilledTransaction, GetValue,
38-
Header, MerkleRoot, OutPoint, SpentOutput, Txid, WithdrawalBundle,
38+
Header, MerkleRoot, OutPoint, SpentOutput, Transaction, TxIn, Txid,
39+
WithdrawalBundle,
3940
},
4041
};
4142

@@ -771,6 +772,9 @@ impl NetTask {
771772
}
772773
}
773774

775+
pub type FilledTransactionWithPosition =
776+
(Authorized<FilledTransaction>, Option<TxIn>);
777+
774778
#[derive(Clone)]
775779
pub struct Node {
776780
archive: Archive,
@@ -893,11 +897,11 @@ impl Node {
893897
Ok(self.archive.get_height(&rotxn, block_hash)?)
894898
}
895899

896-
/// Get blocks in which a tx was included.
900+
/// Get blocks in which a tx was included, and tx index within those blocks
897901
pub fn get_tx_inclusions(
898902
&self,
899903
txid: Txid,
900-
) -> Result<BTreeSet<BlockHash>, Error> {
904+
) -> Result<BTreeMap<BlockHash, u32>, Error> {
901905
let rotxn = self.env.read_txn()?;
902906
Ok(self.archive.get_tx_inclusions(&rotxn, txid)?)
903907
}
@@ -1113,13 +1117,80 @@ impl Node {
11131117
Ok((returned_transactions, fee))
11141118
}
11151119

1120+
/// get a transaction from the archive or mempool, if it exists
1121+
pub fn try_get_transaction(
1122+
&self,
1123+
txid: Txid,
1124+
) -> Result<Option<Transaction>, Error> {
1125+
let rotxn = self.env.read_txn()?;
1126+
if let Some((block_hash, txin)) = self
1127+
.archive
1128+
.get_tx_inclusions(&rotxn, txid)?
1129+
.first_key_value()
1130+
{
1131+
let body = self.archive.get_body(&rotxn, *block_hash)?;
1132+
let tx = body.transactions.into_iter().nth(*txin as usize).unwrap();
1133+
Ok(Some(tx))
1134+
} else if let Some(auth_tx) =
1135+
self.mempool.transactions.get(&rotxn, &txid)?
1136+
{
1137+
Ok(Some(auth_tx.transaction))
1138+
} else {
1139+
Ok(None)
1140+
}
1141+
}
1142+
1143+
/// get a filled transaction from the archive/state or mempool,
1144+
/// and the tx index, if the transaction exists
1145+
/// and can be filled with the current state.
1146+
/// a tx index of `None` indicates that the tx is in the mempool.
1147+
pub fn try_get_filled_transaction(
1148+
&self,
1149+
txid: Txid,
1150+
) -> Result<Option<FilledTransactionWithPosition>, Error> {
1151+
let rotxn = self.env.read_txn()?;
1152+
let tip = self.state.get_tip(&rotxn)?;
1153+
let inclusions = self.archive.get_tx_inclusions(&rotxn, txid)?;
1154+
if let Some((block_hash, idx)) =
1155+
inclusions.into_iter().try_find(|(block_hash, _)| {
1156+
self.archive.is_descendant(&rotxn, *block_hash, tip)
1157+
})?
1158+
{
1159+
let body = self.archive.get_body(&rotxn, block_hash)?;
1160+
let auth_txs = body.authorized_transactions();
1161+
let auth_tx = auth_txs.into_iter().nth(idx as usize).unwrap();
1162+
let filled_tx = self
1163+
.state
1164+
.fill_transaction_from_stxos(&rotxn, auth_tx.transaction)?;
1165+
let auth_tx = Authorized {
1166+
transaction: filled_tx,
1167+
authorizations: auth_tx.authorizations,
1168+
};
1169+
let txin = TxIn { block_hash, idx };
1170+
let res = (auth_tx, Some(txin));
1171+
return Ok(Some(res));
1172+
}
1173+
if let Some(auth_tx) = self.mempool.transactions.get(&rotxn, &txid)? {
1174+
match self.state.fill_authorized_transaction(&rotxn, auth_tx) {
1175+
Ok(filled_tx) => {
1176+
let res = (filled_tx, None);
1177+
Ok(Some(res))
1178+
}
1179+
Err(state::Error::NoUtxo { .. }) => Ok(None),
1180+
Err(err) => Err(err.into()),
1181+
}
1182+
} else {
1183+
Ok(None)
1184+
}
1185+
}
1186+
11161187
pub fn get_pending_withdrawal_bundle(
11171188
&self,
11181189
) -> Result<Option<WithdrawalBundle>, Error> {
1119-
let txn = self.env.read_txn()?;
1190+
let rotxn = self.env.read_txn()?;
11201191
let bundle = self
11211192
.state
1122-
.get_pending_withdrawal_bundle(&txn)?
1193+
.get_pending_withdrawal_bundle(&rotxn)?
11231194
.map(|(bundle, _)| bundle);
11241195
Ok(bundle)
11251196
}

lib/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -581,7 +581,7 @@ impl State {
581581
}
582582

583583
/// Fill a transaction that has already been applied
584-
fn fill_transaction_from_stxos(
584+
pub fn fill_transaction_from_stxos(
585585
&self,
586586
rotxn: &RoTxn,
587587
tx: Transaction,

lib/types/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,3 +465,10 @@ impl PartialOrd for AggregatedWithdrawal {
465465
Some(self.cmp(other))
466466
}
467467
}
468+
469+
/// Transaction index
470+
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
471+
pub struct TxIn {
472+
pub block_hash: BlockHash,
473+
pub idx: u32,
474+
}

rpc-api/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ version.workspace = true
88
bip300301.workspace = true
99
jsonrpsee = { version = "0.20.0", features = ["macros"] }
1010
plain_bitnames = { path = "../lib" }
11+
serde = { version = "1.0.179", features = ["derive"] }
1112

1213
[lib]
1314
name = "plain_bitnames_app_rpc_api"

rpc-api/lib.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@ use bip300301::bitcoin;
66
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
77
use plain_bitnames::types::{
88
hashes::BitName, Address, BitNameData, Block, BlockHash, FilledOutput,
9-
OutPoint, Txid,
9+
OutPoint, Transaction, TxIn, Txid,
1010
};
11+
use serde::{Deserialize, Serialize};
12+
13+
#[derive(Clone, Debug, Deserialize, Serialize)]
14+
pub struct TxInfo {
15+
pub confirmations: Option<u32>,
16+
pub fee_sats: u64,
17+
pub txin: Option<TxIn>,
18+
}
1119

1220
#[rpc(client, server)]
1321
pub trait Rpc {
@@ -46,6 +54,20 @@ pub trait Rpc {
4654
#[method(name = "get_paymail")]
4755
async fn get_paymail(&self) -> RpcResult<HashMap<OutPoint, FilledOutput>>;
4856

57+
/// Get transaction by txid
58+
#[method(name = "get_transaction")]
59+
async fn get_transaction(
60+
&self,
61+
txid: Txid,
62+
) -> RpcResult<Option<Transaction>>;
63+
64+
/// Get information about a transaction in the current chain
65+
#[method(name = "get_transaction_info")]
66+
async fn get_transaction_info(
67+
&self,
68+
txid: Txid,
69+
) -> RpcResult<Option<TxInfo>>;
70+
4971
/// Get the current block count
5072
#[method(name = "getblockcount")]
5173
async fn getblockcount(&self) -> RpcResult<u32>;

0 commit comments

Comments
 (0)