Skip to content

Commit e3afd65

Browse files
Merge pull request #6733 from hstove-stacks/fix/add-block-height-to-get-transaction
feat: add block height to get-transaction RPC
2 parents fdf1041 + 45add60 commit e3afd65

File tree

3 files changed

+54
-5
lines changed

3 files changed

+54
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1212
- Fixed an issue where `event.committed` was always equal to `true` in the block replay RPC endpoint
1313
- Added `result_hex` and `post_condition_aborted` to the block replay RPC endpoint
1414
- Added `--epoch <epoch_number>` flag to `clarity-cli` commands to specify the epoch context for evaluation.
15+
- In the `/v3/transaction/{txid}` RPC endpoint, added `block_height` and `is_canonical` to the response.
1516

1617
### Fixed
1718

stacks-node/src/tests/signer/v0.rs

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use madhouse::{execute_commands, prop_allof, scenario, Command, CommandWrapper};
3434
use pinny::tag;
3535
use proptest::prelude::Strategy;
3636
use rand::{thread_rng, Rng};
37+
use reqwest::header::AUTHORIZATION;
3738
use rusqlite::Connection;
3839
use stacks::address::AddressHashMode;
3940
use stacks::burnchains::Txid;
@@ -67,6 +68,7 @@ use stacks::core::test_util::{
6768
use stacks::core::{StacksEpochId, CHAIN_ID_TESTNET, HELIUM_BLOCK_LIMIT_20};
6869
use stacks::libstackerdb::StackerDBChunkData;
6970
use stacks::net::api::getsigner::GetSignerResponse;
71+
use stacks::net::api::gettransaction::TransactionResponse;
7072
use stacks::net::api::postblock_proposal::{
7173
BlockValidateResponse, ValidateRejectCode, TEST_REJECT_REPLAY_TXS,
7274
TEST_VALIDATE_DELAY_DURATION_SECS, TEST_VALIDATE_STALL,
@@ -3989,6 +3991,7 @@ fn tx_replay_btc_on_stx_invalidation() {
39893991
c.reset_replay_set_after_fork_blocks = 5;
39903992
},
39913993
|node_config| {
3994+
node_config.node.txindex = true;
39923995
node_config.miner.block_commit_delay = Duration::from_secs(1);
39933996
node_config.miner.replay_transactions = true;
39943997
node_config.miner.activated_vrf_key_path =
@@ -4001,7 +4004,7 @@ fn tx_replay_btc_on_stx_invalidation() {
40014004

40024005
let conf = &signer_test.running_nodes.conf;
40034006
let mut miner_keychain = Keychain::default(conf.node.seed.clone()).generate_op_signer();
4004-
let _http_origin = format!("http://{}", &conf.node.rpc_bind);
4007+
let http_origin = format!("http://{}", &conf.node.rpc_bind);
40054008
let mut btc_controller = BitcoinRegtestController::new(conf.clone(), None);
40064009
let submitted_commits = signer_test
40074010
.running_nodes
@@ -4081,7 +4084,7 @@ fn tx_replay_btc_on_stx_invalidation() {
40814084
signer_test.mine_nakamoto_block(Duration::from_secs(30), true);
40824085

40834086
wait_for(30, || {
4084-
let account = get_account(&_http_origin, &recipient_addr);
4087+
let account = get_account(&http_origin, &recipient_addr);
40854088
Ok(account.balance == recipient_balance.into())
40864089
})
40874090
.expect("Timed out waiting for balance to be updated");
@@ -4178,9 +4181,35 @@ fn tx_replay_btc_on_stx_invalidation() {
41784181

41794182
assert!(found_block, "Failed to mine the tenure change block");
41804183
// Ensure that in the 30 seconds, the nonce did not increase. This also asserts that no tx replays were mined.
4181-
let account = get_account(&_http_origin, &recipient_addr);
4184+
let account = get_account(&http_origin, &recipient_addr);
41824185
assert_eq!(account.nonce, 0, "Expected recipient nonce to be 0");
41834186

4187+
// Call `/v3/transaction/{txid}` and verify that `is_canonical` is false
4188+
let get_transaction = |txid: &String| {
4189+
let url = &format!("{http_origin}/v3/transaction/{txid}");
4190+
info!("Send request: GET {url}");
4191+
reqwest::blocking::Client::new()
4192+
.get(url)
4193+
.header(
4194+
AUTHORIZATION,
4195+
conf.connection_options.auth_token.clone().unwrap(),
4196+
)
4197+
.send()
4198+
.unwrap_or_else(|e| panic!("GET request failed: {e}"))
4199+
.json::<TransactionResponse>()
4200+
.unwrap()
4201+
};
4202+
4203+
let transaction = get_transaction(&txid);
4204+
assert!(
4205+
!transaction.is_canonical,
4206+
"Expected transaction response to be non-canonical"
4207+
);
4208+
assert!(
4209+
transaction.block_height.is_none(),
4210+
"Expected block height of tx response to be none"
4211+
);
4212+
41844213
signer_test.shutdown();
41854214
}
41864215

stackslib/src/net/api/gettransaction.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ pub struct TransactionResponse {
3333
pub index_block_hash: StacksBlockId,
3434
pub tx: String,
3535
pub result: String,
36+
pub block_height: Option<u64>,
37+
pub is_canonical: bool,
3638
}
3739

3840
#[derive(Clone)]
@@ -91,7 +93,7 @@ impl RPCRequestHandler for RPCGetTransactionRequestHandler {
9193
fn try_handle_request(
9294
&mut self,
9395
preamble: HttpRequestPreamble,
94-
_contents: HttpRequestContents,
96+
contents: HttpRequestContents,
9597
node: &mut StacksNodeState,
9698
) -> Result<(HttpResponsePreamble, HttpResponseContents), NetError> {
9799
if !node.txindex {
@@ -108,6 +110,13 @@ impl RPCRequestHandler for RPCGetTransactionRequestHandler {
108110
.take()
109111
.ok_or(NetError::SendError("`txid` no set".into()))?;
110112

113+
let tip = match node.load_stacks_chain_tip(&preamble, &contents) {
114+
Ok(tip) => tip,
115+
Err(error_resp) => {
116+
return error_resp.try_into_contents().map_err(NetError::from);
117+
}
118+
};
119+
111120
node.with_node_state(|_network, _sortdb, chainstate, _mempool, _rpc_args| {
112121
let index_block_hash_and_tx_hex_opt = match NakamotoChainState::get_tx_info_from_txid(
113122
chainstate.index_conn().conn(),
@@ -126,11 +135,21 @@ impl RPCRequestHandler for RPCGetTransactionRequestHandler {
126135

127136
match index_block_hash_and_tx_hex_opt {
128137
Some((index_block_hash, tx_hex, result)) => {
138+
let block_height = chainstate
139+
.index_conn()
140+
.get_ancestor_block_height(&index_block_hash, &tip)?;
141+
let is_canonical = chainstate
142+
.index_conn()
143+
.get_ancestor_block_height(&index_block_hash, &tip)
144+
.map(|height_opt| height_opt.is_some())
145+
.unwrap_or(false);
129146
let preamble = HttpResponsePreamble::ok_json(&preamble);
130147
let body = HttpResponseContents::try_from_json(&TransactionResponse {
131-
index_block_hash,
148+
index_block_hash: index_block_hash.clone(),
132149
tx: tx_hex,
133150
result,
151+
block_height,
152+
is_canonical,
134153
})?;
135154
return Ok((preamble, body));
136155
}

0 commit comments

Comments
 (0)