Skip to content

Commit bab0d9a

Browse files
authored
Merge pull request #5942 from rdeioris/feat/txindex
Feat/txindex
2 parents 9241819 + 3f9b09a commit bab0d9a

File tree

40 files changed

+1008
-54
lines changed

40 files changed

+1008
-54
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to the versioning scheme outlined in the [README.md](RE
1313
- Add `max_execution_time_secs` to miner config for limiting duration of contract calls
1414
- When a miner's config file is updated (ie with a new fee rate), a new block commit is issued using
1515
the new values ([#5924](https://github.com/stacks-network/stacks-core/pull/5924))
16+
- Add `txindex` configuration option enabling the storage (and querying via api) of transactions. Note: the old STACKS_TRANSACTION_LOG environment var configuration is no longer available.
1617

1718
### Changed
1819

docs/rpc-endpoints.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,3 +578,20 @@ tenure, `tip_block_id` identifies the highest-known block in this tenure, and
578578
Get number of blocks signed by signer during a given reward cycle
579579

580580
Returns a non-negative integer
581+
582+
### GET /v3/transaction/[Transaction ID]
583+
584+
Returns the index_block_hash, the transaction body (as hex) and the result given the TXID.
585+
586+
```json
587+
{
588+
"index_block_hash": "e0b6c25b1dac0c0e1c75e41ab46bd6d70d9a2d02ffed8f2c0733b6e686289c38",
589+
"tx": "(ok true)",
590+
"result": "808000000004008bc5147525b8f477f0bc4522a88c8339b2494db5000000000000001a0000000000000000010123eab800bc9f639c5aa05d154148a981c89fd21064a6f8cafd8649800a56c9ea77e2e46bed8bd9ef0f173b19b20d6c43e2a9f37b078df5c74b9e6f9be75b650e01020000000007588687edeb02248d402c316ed33e22ea0e73af8703ce5011f3e25f5ce12f00f903ca504742117ee0588687edeb02248d402c316ed33e22ea0e73af87c54e0d94e4dd298cf19778352906a2fcf0af74582b07dfb57c710288874f71ca00000001006bc51b33e9f3626944eb879147e18111581f8f9b"
591+
}
592+
```
593+
594+
This feature requires enabling of transaction indexing by setting the `txindex` node option.
595+
596+
This will return 404 if the transaction does not exist and 501 (Not Implemented) if
597+
transaction indexing is not enabled.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"index_block_hash": "e0b6c25b1dac0c0e1c75e41ab46bd6d70d9a2d02ffed8f2c0733b6e686289c38",
3+
"result": "(ok true)",
4+
"tx": "808000000004008bc5147525b8f477f0bc4522a88c8339b2494db5000000000000001a0000000000000000010123eab800bc9f639c5aa05d154148a981c89fd21064a6f8cafd8649800a56c9ea77e2e46bed8bd9ef0f173b19b20d6c43e2a9f37b078df5c74b9e6f9be75b650e01020000000007588687edeb02248d402c316ed33e22ea0e73af8703ce5011f3e25f5ce12f00f903ca504742117ee0588687edeb02248d402c316ed33e22ea0e73af87c54e0d94e4dd298cf19778352906a2fcf0af74582b07dfb57c710288874f71ca00000001006bc51b33e9f3626944eb879147e18111581f8f9b"
5+
}

docs/rpc/openapi.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -866,3 +866,32 @@ paths:
866866
schema:
867867
type: integer
868868
example: 7
869+
/v3/transaction/{txid}:
870+
post:
871+
summary: Retrieve transaction details by TXID
872+
tags:
873+
- Transactions
874+
description: Get a JSON with the transaction details including the `index_block_hash`, the hex-encoded transaction body, and the `result`.
875+
operationId: get_transaction
876+
parameters:
877+
- name: txid
878+
in: path
879+
required: true
880+
description: Transaction ID
881+
schema:
882+
type: string
883+
responses:
884+
"200":
885+
description: Transaction JSON with index_block_hash, transaction body and result
886+
content:
887+
application/json:
888+
example:
889+
$ref: ./api/core-node/get_transaction.json
890+
"404":
891+
description: Transaction not found
892+
content:
893+
application/text-plain: {}
894+
"501":
895+
description: Transaction indexing not enabled
896+
content:
897+
application/text-plain: {}

stackslib/src/burnchains/affirmation.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,7 @@ fn inner_find_heaviest_block_commit_ptr(
896896
// consider ancestor candidates in _highest_-first order
897897
for ((height, vtxindex), (block_set, burnt)) in ancestor_confirmations.iter().rev() {
898898
let confs = block_set.len() as u64;
899-
if confs < anchor_threshold.into() {
899+
if confs < u64::from(anchor_threshold) {
900900
continue;
901901
}
902902

stackslib/src/chainstate/coordinator/mod.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ pub struct ChainsCoordinatorConfig {
208208
/// true: always wait for canonical anchor blocks, even if it stalls the chain
209209
/// false: proceed to process new chain history even if we're missing an anchor block.
210210
pub require_affirmed_anchor_blocks: bool,
211+
/// true: enable transactions indexing
212+
/// false: no transactions indexing
213+
pub txindex: bool,
211214
}
212215

213216
impl ChainsCoordinatorConfig {
@@ -216,14 +219,16 @@ impl ChainsCoordinatorConfig {
216219
always_use_affirmation_maps: true,
217220
require_affirmed_anchor_blocks: true,
218221
assume_present_anchor_blocks: true,
222+
txindex: false,
219223
}
220224
}
221225

222-
pub fn test_new() -> ChainsCoordinatorConfig {
226+
pub fn test_new(txindex: bool) -> ChainsCoordinatorConfig {
223227
ChainsCoordinatorConfig {
224228
always_use_affirmation_maps: false,
225229
require_affirmed_anchor_blocks: false,
226230
assume_present_anchor_blocks: false,
231+
txindex,
227232
}
228233
}
229234
}
@@ -249,7 +254,7 @@ pub struct ChainsCoordinator<
249254
pub reward_set_provider: R,
250255
pub notifier: N,
251256
pub atlas_config: AtlasConfig,
252-
config: ChainsCoordinatorConfig,
257+
pub config: ChainsCoordinatorConfig,
253258
burnchain_indexer: B,
254259
/// Used to tell the P2P thread that the stackerdb
255260
/// needs to be refreshed.
@@ -646,6 +651,7 @@ impl<T: BlockEventDispatcher, U: RewardSetProvider, B: BurnchainHeaderReader>
646651
path: &str,
647652
reward_set_provider: U,
648653
indexer: B,
654+
txindex: bool,
649655
) -> ChainsCoordinator<'a, T, (), U, (), (), B> {
650656
ChainsCoordinator::test_new_full(
651657
burnchain,
@@ -655,6 +661,7 @@ impl<T: BlockEventDispatcher, U: RewardSetProvider, B: BurnchainHeaderReader>
655661
None,
656662
indexer,
657663
None,
664+
txindex,
658665
)
659666
}
660667

@@ -668,6 +675,7 @@ impl<T: BlockEventDispatcher, U: RewardSetProvider, B: BurnchainHeaderReader>
668675
dispatcher: Option<&'a T>,
669676
burnchain_indexer: B,
670677
atlas_config: Option<AtlasConfig>,
678+
txindex: bool,
671679
) -> ChainsCoordinator<'a, T, (), U, (), (), B> {
672680
let burnchain = burnchain.clone();
673681

@@ -713,7 +721,7 @@ impl<T: BlockEventDispatcher, U: RewardSetProvider, B: BurnchainHeaderReader>
713721
notifier: (),
714722
atlas_config,
715723
atlas_db: Some(atlas_db),
716-
config: ChainsCoordinatorConfig::test_new(),
724+
config: ChainsCoordinatorConfig::test_new(txindex),
717725
burnchain_indexer,
718726
refresh_stacker_db: Arc::new(AtomicBool::new(false)),
719727
in_nakamoto_epoch: false,

stackslib/src/chainstate/coordinator/tests.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,13 +470,15 @@ pub fn make_coordinator<'a>(
470470
path,
471471
OnChainRewardSetProvider(None),
472472
indexer,
473+
false,
473474
)
474475
}
475476

476477
pub fn make_coordinator_atlas<'a>(
477478
path: &str,
478479
burnchain: Option<Burnchain>,
479480
atlas_config: Option<AtlasConfig>,
481+
txindex: bool,
480482
) -> ChainsCoordinator<
481483
'a,
482484
NullEventDispatcher,
@@ -496,6 +498,7 @@ pub fn make_coordinator_atlas<'a>(
496498
None,
497499
indexer,
498500
atlas_config,
501+
txindex,
499502
)
500503
}
501504

@@ -545,6 +548,7 @@ fn make_reward_set_coordinator<'a>(
545548
path,
546549
StubbedRewardSetProvider(addrs),
547550
indexer,
551+
false,
548552
)
549553
}
550554

@@ -1552,7 +1556,7 @@ fn missed_block_commits_2_1() {
15521556
// did we have a bad missed commit in this window?
15531557
// bad missed commits land in the prepare phase.
15541558
let have_bad_missed_commit = b.is_in_prepare_phase(last_bad_op_height)
1555-
&& ix >= MINING_COMMITMENT_WINDOW.into()
1559+
&& ix >= usize::from(MINING_COMMITMENT_WINDOW)
15561560
&& last_bad_op_height + (MINING_COMMITMENT_WINDOW as u64) > tip.block_height;
15571561
if have_bad_missed_commit {
15581562
// bad commit breaks the chain if its PoX outputs are invalid
@@ -4659,6 +4663,7 @@ fn atlas_stop_start() {
46594663
path,
46604664
Some(burnchain_conf.clone()),
46614665
Some(atlas_config.clone()),
4666+
false,
46624667
);
46634668

46644669
coord.handle_new_burnchain_block().unwrap();
@@ -4853,7 +4858,7 @@ fn atlas_stop_start() {
48534858
// now, we'll shut down all the coordinator connections and reopen them
48544859
// to ensure that the queue remains in place
48554860
let coord = (); // dispose of the coordinator, closing all its connections
4856-
let coord = make_coordinator_atlas(path, Some(burnchain_conf), Some(atlas_config));
4861+
let coord = make_coordinator_atlas(path, Some(burnchain_conf), Some(atlas_config), false);
48574862

48584863
let atlas_queue = coord
48594864
.atlas_db
@@ -5162,7 +5167,7 @@ fn test_epoch_verify_active_pox_contract() {
51625167

51635168
let active_pox_contract = b.pox_constants.active_pox_contract(burn_block_height);
51645169

5165-
if burn_block_height <= pox_v1_unlock_ht.into() {
5170+
if burn_block_height <= u64::from(pox_v1_unlock_ht) {
51665171
assert_eq!(active_pox_contract, POX_1_NAME);
51675172
if curr_reward_cycle == 1 {
51685173
// This is a result of the first stack stx sent.

stackslib/src/chainstate/nakamoto/coordinator/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ impl<
801801
&mut self.sortition_db,
802802
&canonical_sortition_tip,
803803
self.dispatcher,
804+
self.config.txindex,
804805
) {
805806
Ok(receipt_opt) => receipt_opt,
806807
Err(ChainstateError::InvalidStacksBlock(msg)) => {

stackslib/src/chainstate/nakamoto/coordinator/tests.rs

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use stacks_common::types::chainstate::{
3434
BurnchainHeaderHash, StacksAddress, StacksBlockId, StacksPrivateKey, StacksPublicKey,
3535
};
3636
use stacks_common::types::{Address, StacksEpoch, StacksEpochId, StacksPublicKeyBuffer};
37-
use stacks_common::util::hash::Hash160;
37+
use stacks_common::util::hash::{to_hex, Hash160};
3838
use stacks_common::util::secp256k1::Secp256k1PrivateKey;
3939
use stacks_common::util::vrf::VRFProof;
4040

@@ -1568,6 +1568,135 @@ fn pox_treatment() {
15681568
);
15691569
}
15701570

1571+
#[test]
1572+
// Test Transactions indexing system
1573+
fn transactions_indexing() {
1574+
let private_key = StacksPrivateKey::from_seed(&[2]);
1575+
let addr = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(&private_key));
1576+
1577+
let num_stackers: u32 = 4;
1578+
let mut signing_key_seed = num_stackers.to_be_bytes().to_vec();
1579+
signing_key_seed.extend_from_slice(&[1, 1, 1, 1]);
1580+
let signing_key = StacksPrivateKey::from_seed(signing_key_seed.as_slice());
1581+
let test_stackers = (0..num_stackers)
1582+
.map(|index| TestStacker {
1583+
signer_private_key: signing_key.clone(),
1584+
stacker_private_key: StacksPrivateKey::from_seed(&index.to_be_bytes()),
1585+
amount: u64::MAX as u128 - 10000,
1586+
pox_addr: Some(PoxAddress::Standard(
1587+
StacksAddress::new(
1588+
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
1589+
Hash160::from_data(&index.to_be_bytes()),
1590+
)
1591+
.unwrap(),
1592+
Some(AddressHashMode::SerializeP2PKH),
1593+
)),
1594+
max_amount: None,
1595+
})
1596+
.collect::<Vec<_>>();
1597+
let test_signers = TestSigners::new(vec![signing_key]);
1598+
let mut pox_constants = TestPeerConfig::default().burnchain.pox_constants;
1599+
pox_constants.reward_cycle_length = 10;
1600+
pox_constants.v2_unlock_height = 21;
1601+
pox_constants.pox_3_activation_height = 26;
1602+
pox_constants.v3_unlock_height = 27;
1603+
pox_constants.pox_4_activation_height = 28;
1604+
1605+
let mut boot_plan = NakamotoBootPlan::new(function_name!())
1606+
.with_test_stackers(test_stackers.clone())
1607+
.with_test_signers(test_signers.clone())
1608+
.with_private_key(private_key)
1609+
.with_txindex(true);
1610+
boot_plan.pox_constants = pox_constants;
1611+
1612+
let mut peer = boot_plan.boot_into_nakamoto_peer(vec![], None);
1613+
1614+
// generate a new block with txindex
1615+
let (tracked_block, burn_height, ..) =
1616+
peer.single_block_tenure(&private_key, |_| {}, |_| {}, |_| false);
1617+
1618+
assert_eq!(peer.try_process_block(&tracked_block).unwrap(), true);
1619+
1620+
let tracked_block_id = tracked_block.block_id();
1621+
1622+
let chainstate = &peer.stacks_node.unwrap().chainstate;
1623+
1624+
// compare transactions to what has been tracked
1625+
for tx in tracked_block.txs {
1626+
let current_tx_hex = to_hex(&tx.serialize_to_vec());
1627+
let (index_block_hash, tx_hex, _) =
1628+
NakamotoChainState::get_tx_info_from_txid(&chainstate.index_conn(), tx.txid())
1629+
.unwrap()
1630+
.unwrap();
1631+
assert_eq!(index_block_hash, tracked_block_id);
1632+
assert_eq!(tx_hex, current_tx_hex);
1633+
}
1634+
}
1635+
1636+
#[test]
1637+
// Test Transactions indexing system (not indexing)
1638+
fn transactions_not_indexing() {
1639+
let private_key = StacksPrivateKey::from_seed(&[2]);
1640+
let addr = StacksAddress::p2pkh(false, &StacksPublicKey::from_private(&private_key));
1641+
1642+
let num_stackers: u32 = 4;
1643+
let mut signing_key_seed = num_stackers.to_be_bytes().to_vec();
1644+
signing_key_seed.extend_from_slice(&[1, 1, 1, 1]);
1645+
let signing_key = StacksPrivateKey::from_seed(signing_key_seed.as_slice());
1646+
let test_stackers = (0..num_stackers)
1647+
.map(|index| TestStacker {
1648+
signer_private_key: signing_key.clone(),
1649+
stacker_private_key: StacksPrivateKey::from_seed(&index.to_be_bytes()),
1650+
amount: u64::MAX as u128 - 10000,
1651+
pox_addr: Some(PoxAddress::Standard(
1652+
StacksAddress::new(
1653+
C32_ADDRESS_VERSION_TESTNET_SINGLESIG,
1654+
Hash160::from_data(&index.to_be_bytes()),
1655+
)
1656+
.unwrap(),
1657+
Some(AddressHashMode::SerializeP2PKH),
1658+
)),
1659+
max_amount: None,
1660+
})
1661+
.collect::<Vec<_>>();
1662+
let test_signers = TestSigners::new(vec![signing_key]);
1663+
let mut pox_constants = TestPeerConfig::default().burnchain.pox_constants;
1664+
pox_constants.reward_cycle_length = 10;
1665+
pox_constants.v2_unlock_height = 21;
1666+
pox_constants.pox_3_activation_height = 26;
1667+
pox_constants.v3_unlock_height = 27;
1668+
pox_constants.pox_4_activation_height = 28;
1669+
1670+
let mut boot_plan = NakamotoBootPlan::new(function_name!())
1671+
.with_test_stackers(test_stackers.clone())
1672+
.with_test_signers(test_signers.clone())
1673+
.with_private_key(private_key)
1674+
.with_txindex(false);
1675+
boot_plan.pox_constants = pox_constants;
1676+
1677+
let mut peer = boot_plan.boot_into_nakamoto_peer(vec![], None);
1678+
1679+
// generate a new block but without txindex
1680+
let (untracked_block, burn_height, ..) =
1681+
peer.single_block_tenure(&private_key, |_| {}, |_| {}, |_| false);
1682+
1683+
assert_eq!(peer.try_process_block(&untracked_block).unwrap(), true);
1684+
1685+
let untracked_block_id = untracked_block.block_id();
1686+
1687+
let chainstate = &peer.stacks_node.unwrap().chainstate;
1688+
1689+
// ensure untracked transactions are not recorded
1690+
for tx in untracked_block.txs {
1691+
assert_eq!(
1692+
NakamotoChainState::get_tx_info_from_txid(&chainstate.index_conn(), tx.txid(),)
1693+
.unwrap()
1694+
.is_none(),
1695+
true
1696+
);
1697+
}
1698+
}
1699+
15711700
/// Test chainstate getters against an instantiated epoch2/Nakamoto chain.
15721701
/// There are 11 epoch2 blocks and 2 nakamto tenure with 10 nakamoto blocks each
15731702
/// Tests:

stackslib/src/chainstate/nakamoto/miner.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -693,7 +693,7 @@ impl BlockBuilder for NakamotoBlockBuilder {
693693
ast_rules: ASTRules,
694694
max_execution_time: Option<std::time::Duration>,
695695
) -> TransactionResult {
696-
if self.bytes_so_far + tx_len >= MAX_EPOCH_SIZE.into() {
696+
if self.bytes_so_far + tx_len >= u64::from(MAX_EPOCH_SIZE) {
697697
return TransactionResult::skipped_due_to_error(tx, Error::BlockTooBigError);
698698
}
699699

0 commit comments

Comments
 (0)