From b1d6be9b91b77f3e24abf1976bdf53158cf28d17 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Tue, 11 Jun 2024 15:49:00 +0200 Subject: [PATCH 1/3] fix: pox events should use same index as associated contract log event (#1994) * fix: pox events should use same index as associated contract log event #1983 * test: add test --- src/event-stream/event-server.ts | 13 +- src/tests/synthetic-stx-txs-tests.ts | 29 + ...904c355a501d6dffa3b5e4799083062469dbc.json | 2357 +++++++++++++++++ 3 files changed, 2396 insertions(+), 3 deletions(-) create mode 100644 src/tests/synthetic-tx-payloads/stx_lock-1994-0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc.json diff --git a/src/event-stream/event-server.ts b/src/event-stream/event-server.ts index 22fc2cd08d..5bbe0bfdfe 100644 --- a/src/event-stream/event-server.ts +++ b/src/event-stream/event-server.ts @@ -480,6 +480,8 @@ function parseDataStoreTxEventData( return dbTx; }); + const poxEventLogs: Map = new Map(); + for (const event of events) { if (!event.committed) { logger.debug(`Ignoring uncommitted tx event from tx ${event.txid}`); @@ -548,6 +550,7 @@ function parseDataStoreTxEventData( ...dbEvent, ...poxEventData, }; + poxEventLogs.set(dbPoxEvent, entry); switch (contractName) { case POX_2_CONTRACT_NAME: { dbTx.pox2Events.push(dbPoxEvent); @@ -723,9 +726,6 @@ function parseDataStoreTxEventData( tx.nftEvents, tx.stxEvents, tx.stxLockEvents, - tx.pox2Events, - tx.pox3Events, - tx.pox4Events, ] .flat() .sort((a, b) => a.event_index - b.event_index); @@ -733,6 +733,13 @@ function parseDataStoreTxEventData( for (let i = 0; i < sortedEvents.length; i++) { sortedEvents[i].event_index = i; } + for (const poxEvent of [tx.pox2Events, tx.pox3Events, tx.pox4Events].flat()) { + const associatedLogEvent = poxEventLogs.get(poxEvent); + if (!associatedLogEvent) { + throw new Error(`Missing associated contract log event for pox event ${poxEvent.tx_id}`); + } + poxEvent.event_index = associatedLogEvent.event_index; + } } return dbData; diff --git a/src/tests/synthetic-stx-txs-tests.ts b/src/tests/synthetic-stx-txs-tests.ts index cd9b7dd278..c13270bd96 100644 --- a/src/tests/synthetic-stx-txs-tests.ts +++ b/src/tests/synthetic-stx-txs-tests.ts @@ -4,6 +4,7 @@ import * as path from 'path'; import { DecodedTxResult, TxPayloadTypeID } from 'stacks-encoding-native-js'; import { CoreNodeBlockMessage } from '../event-stream/core-node-message'; import { parseMessageTransaction } from '../event-stream/reader'; +import { parseNewBlockMessage } from '../event-stream/event-server'; // Test processing of the psuedo-Stacks transactions, i.e. the ones that // originate on the Bitcoin chain, and have a `raw_tx == '0x00'. @@ -194,6 +195,34 @@ describe('synthetic stx txs', () => { expect(parsed.parsed_tx).toEqual(expect.objectContaining(expected)); }); + test('test synthetic tx stx lock 3', () => { + const file = + 'synthetic-tx-payloads/stx_lock-1994-0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc.json'; + const txid = file.split('-').slice(-1)[0].split('.')[0]; + const payloadStr = fs.readFileSync(path.join(__dirname, file), { encoding: 'utf8' }); + const blockMsg = JSON.parse(payloadStr) as CoreNodeBlockMessage; + const txMsg = blockMsg.transactions.find(t => t.txid === txid); + if (!txMsg) { + throw new Error(`Cound not find tx ${txid}`); + } + const parsed = parseNewBlockMessage(ChainID.Mainnet, blockMsg); + if (!parsed) { + throw new Error(`Failed to parse ${txid}`); + } + // Ensure real contract event indexes are contiguous + const events = [parsed.txs[0].contractLogEvents, parsed.txs[0].stxLockEvents] + .flat() + .sort((a, b) => a.event_index - b.event_index); + expect(events).toHaveLength(13); + for (let i = 0; i < events.length; i++) { + expect(events[i].event_index).toEqual(i); + } + // Ensure synthetic pox event indexes are in expected range + for (const poxEvent of parsed.txs[0].pox4Events) { + expect(poxEvent.event_index).toBeLessThan(events.length); + } + }); + test('test synthetic tx stx lock 2', () => { // Testing a newer tx from mainnet (at block 51451) const file = diff --git a/src/tests/synthetic-tx-payloads/stx_lock-1994-0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc.json b/src/tests/synthetic-tx-payloads/stx_lock-1994-0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc.json new file mode 100644 index 0000000000..8af2e36c61 --- /dev/null +++ b/src/tests/synthetic-tx-payloads/stx_lock-1994-0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc.json @@ -0,0 +1,2357 @@ +{ + "block_hash": "0x5d53911701b66adcdda018001ebe1a7d54acd4f9a8d15d1f57cf4b2058b78f95", + "miner_txid": "0x22f30c8696f15dda1bf32cd1f5d0a44e242cf19f1ccc0613b8566cfdf79b5cfe", + "reward_set": null, + "block_height": 1, + "cycle_number": null, + "anchored_cost": { + "runtime": 402683495, + "read_count": 10185, + "read_length": 25959775, + "write_count": 700, + "write_length": 70075 + }, + "signer_bitvec": null, + "burn_block_hash": "0x00000000000000000002f2e8c7fd60addc520b09467f618667a841e2ceafa6cb", + "burn_block_time": 1716238792, + "index_block_hash": "0x80e73cd9a61876db38f4e02828241076d0a16670fc11646abb74d634804cc8d9", + "burn_block_height": 844350, + "parent_block_hash": "0x042cc20f6105fb9e742c4c2369a05999ee766bfa21fe66e465ebb2de75c3a36d", + "parent_microblock": "0x0000000000000000000000000000000000000000000000000000000000000000", + "pox_v1_unlock_height": 781552, + "pox_v2_unlock_height": 787652, + "pox_v3_unlock_height": 840361, + "matured_miner_rewards": [ + { + "recipient": "SP11M3BVG5TGNWQ1FFE5EJM86TMV08YF3EK0JSMQ8", + "miner_address": "SP11M3BVG5TGNWQ1FFE5EJM86TMV08YF3EK0JSMQ8", + "coinbase_amount": "1000000000", + "tx_fees_anchored": "9511437", + "from_stacks_block_hash": "0x040516e4ae9bbbaf22c4b4e716f359bd9a7b90b29f47c17e8d0df24af8242e20", + "from_index_consensus_hash": "0xb94814bef09b858177fe471b917273a63d899c25f0eb0f6210a07733e8c0ea0e", + "tx_fees_streamed_produced": "0", + "tx_fees_streamed_confirmed": "0" + }, + { + "recipient": "SP28YSGNKBZM8VE1ZQF8A3ANG9MZ7Q5893XASMGWM", + "miner_address": "SP28YSGNKBZM8VE1ZQF8A3ANG9MZ7Q5893XASMGWM", + "coinbase_amount": "0", + "tx_fees_anchored": "0", + "from_stacks_block_hash": "0x040516e4ae9bbbaf22c4b4e716f359bd9a7b90b29f47c17e8d0df24af8242e20", + "from_index_consensus_hash": "0xb94814bef09b858177fe471b917273a63d899c25f0eb0f6210a07733e8c0ea0e", + "tx_fees_streamed_produced": "0", + "tx_fees_streamed_confirmed": "0" + } + ], + "parent_burn_block_hash": "0x00000000000000000002dcef0ad9a999961f87665cbda4fc728fd8ba2b63c419", + "parent_index_block_hash": "0x6245f25fbfbdc9bc1e48e10c091930f6a25b3b77eb483e8b61b1c18a848f8599", + "parent_burn_block_height": 844349, + "confirmed_microblocks_cost": { + "runtime": 0, + "read_count": 0, + "read_length": 0, + "write_count": 0, + "write_length": 0 + }, + "parent_microblock_sequence": 0, + "parent_burn_block_timestamp": 1716237637, + "transactions": [ + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "raw_tx": "0x0000000001040020a586c3f6c204b8429f5f2f50c384c101057ee9000000000000000e00000000000186a00000ed9a37094eabba548ba149245e4f1224c8194b3bff2084b5987d96d27707273e61691671ed12f06d80b8ebbf0706de458370bd2e3a9682a58db7db8a66ce9a9b0101000000000214e75dff23072877a139ecf7b7f899aa714b7fe97f207075626c69632d706f6f6c732d73747261746567792d6d616e616765722d76320d66756e642d7374726174656779000000010b0000000f0100000000000000000000027f59b6d40a010000000000000000000000e8d4a510000100000000000000000000003a35294400010000000000000000000000000000000001000000000000000000000002540be400010000000000000000000000000000000001000000000000000000000000000000000100000000000000000000000000000000010000000000000000000000000000000001000000000000000000000000000000000100000000000000000000027f59b6d40a01000000000000000000000000000000000100000000000000000000003a35294400010000000000000000000000174876e80001000000000000000000000002540be400", + "status": "success", + "tx_index": 40, + "raw_result": "0x070100000000000000000000000000000000", + "burnchain_op": null, + "contract_abi": null, + "execution_cost": { + "runtime": 6560983, + "read_count": 596, + "read_length": 4230411, + "write_count": 38, + "write_length": 3669 + }, + "microblock_hash": null, + "microblock_sequence": null, + "microblock_parent_hash": null + } + ], + "events": [ + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 105, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "delegate-to": { + "Principal": { + "Contract": { + "name": "pox4-fast-pool-v3", + "issuer": [ + 22, + [ + 131, + 237, + 102, + 134, + 3, + 21, + 227, + 52, + 1, + 11, + 191, + 183, + 110, + 179, + 238, + 248, + 135, + 239, + 238, + 10 + ] + ] + } + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + } + }, + "type_signature": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 114, + 101, + 118, + 111, + 107, + 101, + 45, + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 2745989256201 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "fastpool-v2-member1", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 19 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000030b64656c65676174652d746f061683ed66860315e334010bbfb76eb3eef887efee0a11706f78342d666173742d706f6f6c2d76330c656e642d6379636c652d6964090e73746172742d6379636c652d69640100000000000000000000000000000055066c6f636b65640100000000000000000000027f59b6d409046e616d650d000000137265766f6b652d64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f1366617374706f6f6c2d76322d6d656d62657231", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 111, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + } + }, + "type_signature": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 114, + 101, + 118, + 111, + 107, + 101, + 45, + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 249999000000 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member3", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 19 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000030b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d6964090e73746172742d6379636c652d69640100000000000000000000000000000055066c6f636b65640100000000000000000000003a351a01c0046e616d650d000000137265766f6b652d64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657233", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 115, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + } + }, + "type_signature": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 114, + 101, + 118, + 111, + 107, + 101, + 45, + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 9999000000 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member5", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 19 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000030b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d6964090e73746172742d6379636c652d69640100000000000000000000000000000055066c6f636b65640100000000000000000000000253fca1c0046e616d650d000000137265766f6b652d64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657235", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 112, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "pox-addr": { + "Optional": { + "data": null + } + }, + "amount-ustx": { + "UInt": 250000000000 + }, + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + }, + "unlock-burn-height": { + "Optional": { + "data": null + } + } + }, + "type_signature": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 249999000000 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member3", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 12 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000060b616d6f756e742d757374780100000000000000000000003a352944000b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d69640908706f782d61646472090e73746172742d6379636c652d6964010000000000000000000000000000005512756e6c6f636b2d6275726e2d68656967687409066c6f636b65640100000000000000000000003a351a01c0046e616d650d0000000c64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657233", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 114, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "pox-addr": { + "Optional": { + "data": null + } + }, + "amount-ustx": { + "UInt": 100000000000 + }, + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + }, + "unlock-burn-height": { + "Optional": { + "data": null + } + } + }, + "type_signature": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 99999000000 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member4", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 12 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000060b616d6f756e742d75737478010000000000000000000000174876e8000b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d69640908706f782d61646472090e73746172742d6379636c652d6964010000000000000000000000000000005512756e6c6f636b2d6275726e2d68656967687409066c6f636b6564010000000000000000000000174867a5c0046e616d650d0000000c64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657234", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 109, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + } + }, + "type_signature": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 114, + 101, + 118, + 111, + 107, + 101, + 45, + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 2745989256201 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member1", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 19 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000030b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d6964090e73746172742d6379636c652d69640100000000000000000000000000000055066c6f636b65640100000000000000000000027f59b6d409046e616d650d000000137265766f6b652d64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657231", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 106, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "pox-addr": { + "Optional": { + "data": null + } + }, + "amount-ustx": { + "UInt": 2745989256202 + }, + "delegate-to": { + "Principal": { + "Contract": { + "name": "pox4-fast-pool-v3", + "issuer": [ + 22, + [ + 131, + 237, + 102, + 134, + 3, + 21, + 227, + 52, + 1, + 11, + 191, + 183, + 110, + 179, + 238, + 248, + 135, + 239, + 238, + 10 + ] + ] + } + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + }, + "unlock-burn-height": { + "Optional": { + "data": null + } + } + }, + "type_signature": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 2745989256201 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "fastpool-v2-member1", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 12 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000060b616d6f756e742d757374780100000000000000000000027f59b6d40a0b64656c65676174652d746f061683ed66860315e334010bbfb76eb3eef887efee0a11706f78342d666173742d706f6f6c2d76330c656e642d6379636c652d69640908706f782d61646472090e73746172742d6379636c652d6964010000000000000000000000000000005512756e6c6f636b2d6275726e2d68656967687409066c6f636b65640100000000000000000000027f59b6d409046e616d650d0000000c64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f1366617374706f6f6c2d76322d6d656d62657231", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 116, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "pox-addr": { + "Optional": { + "data": null + } + }, + "amount-ustx": { + "UInt": 10000000000 + }, + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + }, + "unlock-burn-height": { + "Optional": { + "data": null + } + } + }, + "type_signature": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 9999000000 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member5", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 12 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000060b616d6f756e742d7573747801000000000000000000000002540be4000b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d69640908706f782d61646472090e73746172742d6379636c652d6964010000000000000000000000000000005512756e6c6f636b2d6275726e2d68656967687409066c6f636b65640100000000000000000000000253fca1c0046e616d650d0000000c64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657235", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 113, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + } + }, + "type_signature": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 114, + 101, + 118, + 111, + 107, + 101, + 45, + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 99999000000 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member4", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType" + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 19 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000030b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d6964090e73746172742d6379636c652d69640100000000000000000000000000000055066c6f636b6564010000000000000000000000174867a5c0046e616d650d000000137265766f6b652d64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657234", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 107, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "stacker": { + "Principal": { + "Contract": { + "name": "fastpool-v2-member1", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "pox-addr": { + "Tuple": { + "data_map": { + "version": { + "Sequence": { + "Buffer": { + "data": [ + 4 + ] + } + } + }, + "hashbytes": { + "Sequence": { + "Buffer": { + "data": [ + 131, + 237, + 102, + 134, + 3, + 21, + 227, + 52, + 1, + 11, + 191, + 183, + 110, + 179, + 238, + 248, + 135, + 239, + 238, + 10 + ] + } + } + } + }, + "type_signature": { + "type_map": { + "version": { + "SequenceType": { + "BufferType": 1 + } + }, + "hashbytes": { + "SequenceType": { + "BufferType": 20 + } + } + } + } + } + }, + "delegator": { + "Principal": { + "Contract": { + "name": "pox4-fast-pool-v3", + "issuer": [ + 22, + [ + 131, + 237, + 102, + 134, + 3, + 21, + 227, + 52, + 1, + 11, + 191, + 183, + 110, + 179, + 238, + 248, + 135, + 239, + 238, + 10 + ] + ] + } + } + }, + "end-cycle-id": { + "Optional": { + "data": { + "UInt": 86 + } + } + }, + "extend-count": { + "UInt": 1 + }, + "start-cycle-id": { + "UInt": 85 + }, + "unlock-burn-height": { + "UInt": 846650 + } + }, + "type_signature": { + "type_map": { + "stacker": "PrincipalType", + "pox-addr": { + "TupleType": { + "type_map": { + "version": { + "SequenceType": { + "BufferType": 1 + } + }, + "hashbytes": { + "SequenceType": { + "BufferType": 20 + } + } + } + } + }, + "delegator": "PrincipalType", + "end-cycle-id": { + "OptionalType": "UIntType" + }, + "extend-count": "UIntType", + "start-cycle-id": "UIntType", + "unlock-burn-height": "UIntType" + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 97, + 99, + 107, + 45, + 101, + 120, + 116, + 101, + 110, + 100 + ] + } + } + } + }, + "locked": { + "UInt": 2745989256201 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "fastpool-v2-member1", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "stacker": "PrincipalType", + "pox-addr": { + "TupleType": { + "type_map": { + "version": { + "SequenceType": { + "BufferType": 1 + } + }, + "hashbytes": { + "SequenceType": { + "BufferType": 20 + } + } + } + } + }, + "delegator": "PrincipalType", + "end-cycle-id": { + "OptionalType": "UIntType" + }, + "extend-count": "UIntType", + "start-cycle-id": "UIntType", + "unlock-burn-height": "UIntType" + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 21 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000070964656c656761746f72061683ed66860315e334010bbfb76eb3eef887efee0a11706f78342d666173742d706f6f6c2d76330c656e642d6379636c652d69640a01000000000000000000000000000000560c657874656e642d636f756e74010000000000000000000000000000000108706f782d616464720c0000000209686173686279746573020000001483ed66860315e334010bbfb76eb3eef887efee0a0776657273696f6e02000000010407737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f1366617374706f6f6c2d76322d6d656d626572310e73746172742d6379636c652d6964010000000000000000000000000000005512756e6c6f636b2d6275726e2d68656967687401000000000000000000000000000ceb3a066c6f636b65640100000000000000000000027f59b6d409046e616d650d0000001564656c65676174652d737461636b2d657874656e6407737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f1366617374706f6f6c2d76322d6d656d62657231", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 110, + "contract_event": { + "topic": "print", + "value": { + "Response": { + "data": { + "Tuple": { + "data_map": { + "data": { + "Tuple": { + "data_map": { + "pox-addr": { + "Optional": { + "data": null + } + }, + "amount-ustx": { + "UInt": 2745989256202 + }, + "delegate-to": { + "Principal": { + "Standard": [ + 22, + [ + 59, + 188, + 101, + 209, + 18, + 231, + 158, + 169, + 210, + 0, + 30, + 21, + 248, + 166, + 21, + 136, + 86, + 149, + 216, + 182 + ] + ] + } + }, + "end-cycle-id": { + "Optional": { + "data": null + } + }, + "start-cycle-id": { + "UInt": 85 + }, + "unlock-burn-height": { + "Optional": { + "data": null + } + } + }, + "type_signature": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + } + }, + "name": { + "Sequence": { + "String": { + "ASCII": { + "data": [ + 100, + 101, + 108, + 101, + 103, + 97, + 116, + 101, + 45, + 115, + 116, + 120 + ] + } + } + } + }, + "locked": { + "UInt": 2745989256201 + }, + "balance": { + "UInt": 1000000 + }, + "stacker": { + "Principal": { + "Contract": { + "name": "xverse-v2-member1", + "issuer": [ + 20, + [ + 231, + 93, + 255, + 35, + 7, + 40, + 119, + 161, + 57, + 236, + 247, + 183, + 248, + 153, + 170, + 113, + 75, + 127, + 233, + 127 + ] + ] + } + } + }, + "burnchain-unlock-height": { + "UInt": 844550 + } + }, + "type_signature": { + "type_map": { + "data": { + "TupleType": { + "type_map": { + "pox-addr": { + "OptionalType": "NoType" + }, + "amount-ustx": "UIntType", + "delegate-to": "PrincipalType", + "end-cycle-id": { + "OptionalType": "NoType" + }, + "start-cycle-id": "UIntType", + "unlock-burn-height": { + "OptionalType": "NoType" + } + } + } + }, + "name": { + "SequenceType": { + "StringType": { + "ASCII": 12 + } + } + }, + "locked": "UIntType", + "balance": "UIntType", + "stacker": "PrincipalType", + "burnchain-unlock-height": "UIntType" + } + } + } + }, + "committed": true + } + }, + "raw_value": "0x070c000000060762616c616e636501000000000000000000000000000f4240176275726e636861696e2d756e6c6f636b2d68656967687401000000000000000000000000000ce30604646174610c000000060b616d6f756e742d757374780100000000000000000000027f59b6d40a0b64656c65676174652d746f05163bbc65d112e79ea9d2001e15f8a615885695d8b60c656e642d6379636c652d69640908706f782d61646472090e73746172742d6379636c652d6964010000000000000000000000000000005512756e6c6f636b2d6275726e2d68656967687409066c6f636b65640100000000000000000000027f59b6d409046e616d650d0000000c64656c65676174652d73747807737461636b65720614e75dff23072877a139ecf7b7f899aa714b7fe97f117876657273652d76322d6d656d62657231", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "stx_lock_event", + "committed": true, + "event_index": 108, + "stx_lock_event": { + "locked_amount": "2745989256201", + "unlock_height": "846650", + "locked_address": "SM3KNVZS30WM7F89SXKVVFY4SN9RMPZZ9FX929N0V.fastpool-v2-member1", + "contract_identifier": "SP000000000000000000002Q6VF78.pox-4" + } + }, + { + "txid": "0xd45e090ac442380cf50655e3d1c904c355a501d6dffa3b5e4799083062469dbc", + "type": "contract_event", + "committed": true, + "event_index": 117, + "contract_event": { + "topic": "print", + "value": { + "Sequence": { + "List": { + "data": [ + { + "Response": { + "data": { + "UInt": 0 + }, + "committed": true + } + }, + { + "Response": { + "data": { + "UInt": 603 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 603 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 34 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 603 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 18000 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 18000 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 18000 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 18000 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 18000 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 0 + }, + "committed": true + } + }, + { + "Response": { + "data": { + "UInt": 34 + }, + "committed": false + } + }, + { + "Response": { + "data": { + "UInt": 0 + }, + "committed": true + } + }, + { + "Response": { + "data": { + "UInt": 0 + }, + "committed": true + } + }, + { + "Response": { + "data": { + "UInt": 0 + }, + "committed": true + } + } + ], + "type_signature": { + "max_len": 15, + "entry_type": { + "ResponseType": [ + "UIntType", + "UIntType" + ] + } + } + } + } + }, + "raw_value": "0x0b0000000f07010000000000000000000000000000000008010000000000000000000000000000025b08010000000000000000000000000000025b08010000000000000000000000000000002208010000000000000000000000000000025b080100000000000000000000000000004650080100000000000000000000000000004650080100000000000000000000000000004650080100000000000000000000000000004650080100000000000000000000000000004650070100000000000000000000000000000000080100000000000000000000000000000022070100000000000000000000000000000000070100000000000000000000000000000000070100000000000000000000000000000000", + "contract_identifier": "SM3KNVZS30WM7F89SXKVVFY4SN9RMPZZ9FX929N0V.public-pools-strategy-v2" + } + } + ] +} From 03a91ed6ccf90d84740ce5f4c794c59b8d3d2de2 Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Mon, 17 Jun 2024 12:56:31 +0200 Subject: [PATCH 2/3] chore: remove sql.unsafe usages (#2001) * chore: remove sql.unsafe usages * chore: remove sql.unsafe usages --- src/datastore/helpers.ts | 19 ++------- src/datastore/pg-store.ts | 84 +++++++++++++++++++-------------------- 2 files changed, 45 insertions(+), 58 deletions(-) diff --git a/src/datastore/helpers.ts b/src/datastore/helpers.ts index 105814404d..1a8566aefa 100644 --- a/src/datastore/helpers.ts +++ b/src/datastore/helpers.ts @@ -273,29 +273,18 @@ export function prefixedCols(columns: string[], prefix: string): string[] { return columns.map(c => `${prefix}.${c}`); } -/** - * Concatenates column names to use on a query. Necessary when one or more of those columns is complex enough - * so that postgres.js can't figure out how to list it (e.g. abi column, aggregates, partitions, etc.). - * @param sql - SQL client - * @param columns - list of columns - * @returns raw SQL column list string - */ -export function unsafeCols(sql: PgSqlClient, columns: string[]): postgres.PendingQuery { - return sql.unsafe(columns.join(', ')); -} - /** * Shorthand function that returns a column query to retrieve the smart contract abi when querying transactions * that may be of type `contract_call`. Usually used alongside `TX_COLUMNS` or `MEMPOOL_TX_COLUMNS`. * @param tableName - Name of the table that will determine the transaction type. Defaults to `txs`. * @returns `string` - abi column select statement portion */ -export function abiColumn(tableName: string = 'txs'): string { - return ` - CASE WHEN ${tableName}.type_id = ${DbTxTypeId.ContractCall} THEN ( +export function abiColumn(sql: PgSqlClient, tableName: string = 'txs'): postgres.Fragment { + return sql` + CASE WHEN ${sql(tableName)}.type_id = ${DbTxTypeId.ContractCall} THEN ( SELECT abi FROM smart_contracts - WHERE smart_contracts.contract_id = ${tableName}.contract_call_contract_id + WHERE smart_contracts.contract_id = ${sql(tableName)}.contract_call_contract_id ORDER BY abi != 'null' DESC, canonical DESC, microblock_canonical DESC, block_height DESC LIMIT 1 ) END as abi diff --git a/src/datastore/pg-store.ts b/src/datastore/pg-store.ts index e335c7c038..3a95734b4b 100644 --- a/src/datastore/pg-store.ts +++ b/src/datastore/pg-store.ts @@ -88,7 +88,6 @@ import { POX_SYNTHETIC_EVENT_COLUMNS, prefixedCols, TX_COLUMNS, - unsafeCols, validateZonefileHash, } from './helpers'; import { PgNotifier } from './pg-notifier'; @@ -240,7 +239,7 @@ export class PgStore extends BasePgStore { const microblock_tx_count: Record = {}; if (metadata?.txs) { const txQuery = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE index_block_hash = ${block.result.index_block_hash} AND canonical = true AND microblock_canonical = true @@ -528,7 +527,7 @@ export class PgStore extends BasePgStore { throw new Error(`Could not find block by hash ${blockHash}`); } const result = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE index_block_hash = ${blockQuery.result.index_block_hash} AND canonical = true AND microblock_canonical = true @@ -613,7 +612,7 @@ export class PgStore extends BasePgStore { const { block_height } = await this.getChainTip(sql); const unanchoredBlockHeight = block_height + 1; const query = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE canonical = true AND microblock_canonical = true AND block_height = ${unanchoredBlockHeight} ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC @@ -823,7 +822,7 @@ export class PgStore extends BasePgStore { AND index_block_hash = ${blockQuery.result.index_block_hash} `; const result = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE canonical = true AND microblock_canonical = true AND index_block_hash = ${blockQuery.result.index_block_hash} @@ -977,7 +976,7 @@ export class PgStore extends BasePgStore { } return await this.sqlTransaction(async sql => { const result = await sql` - SELECT ${unsafeCols(sql, [...MEMPOOL_TX_COLUMNS, abiColumn('mempool_txs')])} + SELECT ${sql(MEMPOOL_TX_COLUMNS)}, ${abiColumn(sql, 'mempool_txs')} FROM mempool_txs WHERE tx_id IN ${sql(args.txIds)} `; @@ -996,7 +995,7 @@ export class PgStore extends BasePgStore { }): Promise> { return await this.sqlTransaction(async sql => { const result = await sql` - SELECT ${unsafeCols(sql, [...MEMPOOL_TX_COLUMNS, abiColumn('mempool_txs')])} + SELECT ${sql(MEMPOOL_TX_COLUMNS)}, ${abiColumn(sql, 'mempool_txs')} FROM mempool_txs WHERE tx_id = ${txId} `; @@ -1048,11 +1047,9 @@ export class PgStore extends BasePgStore { DbTxStatus.DroppedProblematic, ]; const resultQuery = await sql<(MempoolTxQueryResult & { count: number })[]>` - SELECT ${unsafeCols(sql, [ - ...prefixedCols(MEMPOOL_TX_COLUMNS, 'mempool'), - abiColumn('mempool'), - '(COUNT(*) OVER())::INTEGER AS count', - ])} + SELECT ${sql(prefixedCols(MEMPOOL_TX_COLUMNS, 'mempool'))}, + ${abiColumn(sql, 'mempool')}, + (COUNT(*) OVER())::INTEGER AS count FROM ( SELECT * FROM mempool_txs @@ -1323,7 +1320,7 @@ export class PgStore extends BasePgStore { orderBy == 'fee' ? sql`fee_rate` : orderBy == 'size' ? sql`tx_size` : sql`receipt_time`; const orderSql = order == 'asc' ? sql`ASC` : sql`DESC`; const resultQuery = await sql<(MempoolTxQueryResult & { count: number })[]>` - SELECT ${unsafeCols(sql, [...MEMPOOL_TX_COLUMNS, abiColumn('mempool_txs')])}, ${count} + SELECT ${sql(MEMPOOL_TX_COLUMNS)}, ${abiColumn(sql, 'mempool_txs')}, ${count} FROM mempool_txs WHERE ${ address @@ -1386,7 +1383,7 @@ export class PgStore extends BasePgStore { return await this.sqlTransaction(async sql => { const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored }); const result = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE tx_id = ${txId} AND block_height <= ${maxBlockHeight} ORDER BY canonical DESC, microblock_canonical DESC, block_height DESC @@ -1434,7 +1431,7 @@ export class PgStore extends BasePgStore { FROM chain_tip `; resultQuery = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE canonical = true AND microblock_canonical = true AND block_height <= ${maxHeight} ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC @@ -1450,7 +1447,7 @@ export class PgStore extends BasePgStore { AND type_id IN ${sql(txTypeIds)} AND block_height <= ${maxHeight} `; resultQuery = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE canonical = true AND microblock_canonical = true AND type_id IN ${sql(txTypeIds)} AND block_height <= ${maxHeight} @@ -1474,11 +1471,13 @@ export class PgStore extends BasePgStore { }): Promise<{ results: DbEvent[] }> { return await this.sqlTransaction(async sql => { if (args.txs.length === 0) return { results: [] }; - // TODO: This hack has to be done because postgres.js can't figure out how to interpolate - // these `bytea` VALUES comparisons yet. - const transactionValues = args.txs - .map(tx => `('\\x${tx.txId.slice(2)}'::bytea, '\\x${tx.indexBlockHash.slice(2)}'::bytea)`) - .join(', '); + + const transactionValues = args.txs.map(tx => [ + `\\x${tx.txId.slice(2)}`, + `\\x${tx.indexBlockHash.slice(2)}`, + ]); + const txValuesSql = sql(transactionValues.map(tx => sql`(${tx[0]}::bytea, ${tx[1]}::bytea)`)); + const eventIndexStart = args.offset; const eventIndexEnd = args.offset + args.limit - 1; const stxLockResults = await sql< @@ -1497,7 +1496,7 @@ export class PgStore extends BasePgStore { SELECT event_index, tx_id, tx_index, block_height, canonical, locked_amount, unlock_height, locked_address, contract_name FROM stx_lock_events - WHERE (tx_id, index_block_hash) IN (VALUES ${sql.unsafe(transactionValues)}) + WHERE (tx_id, index_block_hash) IN ${txValuesSql} AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const stxResults = await sql< @@ -1517,7 +1516,7 @@ export class PgStore extends BasePgStore { SELECT event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, amount, memo FROM stx_events - WHERE (tx_id, index_block_hash) IN (VALUES ${sql.unsafe(transactionValues)}) + WHERE (tx_id, index_block_hash) IN ${txValuesSql} AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const ftResults = await sql< @@ -1537,7 +1536,7 @@ export class PgStore extends BasePgStore { SELECT event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, asset_identifier, amount FROM ft_events - WHERE (tx_id, index_block_hash) IN (VALUES ${sql.unsafe(transactionValues)}) + WHERE (tx_id, index_block_hash) IN ${txValuesSql} AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const nftResults = await sql< @@ -1557,7 +1556,7 @@ export class PgStore extends BasePgStore { SELECT event_index, tx_id, tx_index, block_height, canonical, asset_event_type_id, sender, recipient, asset_identifier, value FROM nft_events - WHERE (tx_id, index_block_hash) = ANY(VALUES ${sql.unsafe(transactionValues)}) + WHERE (tx_id, index_block_hash) IN ${txValuesSql} AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; const logResults = await sql< @@ -1575,7 +1574,7 @@ export class PgStore extends BasePgStore { SELECT event_index, tx_id, tx_index, block_height, canonical, contract_identifier, topic, value FROM contract_logs - WHERE (tx_id, index_block_hash) IN (VALUES ${sql.unsafe(transactionValues)}) + WHERE (tx_id, index_block_hash) IN ${txValuesSql} AND microblock_canonical = true AND event_index BETWEEN ${eventIndexStart} AND ${eventIndexEnd} `; return { @@ -2745,7 +2744,9 @@ export class PgStore extends BasePgStore { LIMIT ${args.limit} OFFSET ${args.offset} ) - SELECT ${unsafeCols(this.sql, [...TX_COLUMNS, abiColumn(), 'count'])} + SELECT ${this.sql(TX_COLUMNS)}, + ${abiColumn(this.sql)}, + count FROM stx_txs INNER JOIN txs USING (tx_id, index_block_hash, microblock_hash) `; @@ -2814,7 +2815,7 @@ export class PgStore extends BasePgStore { events.sender as event_sender, events.recipient as event_recipient, events.memo as event_memo, - ${this.sql.unsafe(abiColumn('transactions'))} + ${abiColumn(this.sql, 'transactions')} FROM transactions LEFT JOIN events ON transactions.tx_id = events.tx_id AND transactions.tx_id = ${tx_id} @@ -2914,7 +2915,7 @@ export class PgStore extends BasePgStore { ) SELECT transactions.*, - ${this.sql.unsafe(abiColumn('transactions'))}, + ${abiColumn(this.sql, 'transactions')}, events.event_index as event_index, events.event_type_id as event_type, events.amount as event_amount, @@ -3039,7 +3040,7 @@ export class PgStore extends BasePgStore { // TODO(mb): add support for searching for microblock by hash return await this.sqlTransaction(async sql => { const txQuery = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE tx_id = ${hash} LIMIT 1 `; if (txQuery.length > 0) { @@ -3054,7 +3055,7 @@ export class PgStore extends BasePgStore { }; } const txMempoolQuery = await sql` - SELECT ${unsafeCols(sql, [...MEMPOOL_TX_COLUMNS, abiColumn('mempool_txs')])} + SELECT ${sql(MEMPOOL_TX_COLUMNS)}, ${abiColumn(sql, 'mempool_txs')} FROM mempool_txs WHERE pruned = false AND tx_id = ${hash} LIMIT 1 `; if (txMempoolQuery.length > 0) { @@ -3099,7 +3100,7 @@ export class PgStore extends BasePgStore { return await this.sqlTransaction(async sql => { if (isContract) { const contractMempoolTxResult = await sql` - SELECT ${unsafeCols(sql, [...MEMPOOL_TX_COLUMNS, abiColumn('mempool_txs')])} + SELECT ${sql(MEMPOOL_TX_COLUMNS)}, ${abiColumn(sql, 'mempool_txs')} FROM mempool_txs WHERE pruned = false AND smart_contract_contract_id = ${principal} LIMIT 1 `; if (contractMempoolTxResult.length > 0) { @@ -3114,7 +3115,7 @@ export class PgStore extends BasePgStore { }; } const contractTxResult = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE smart_contract_contract_id = ${principal} ORDER BY canonical DESC, microblock_canonical DESC, block_height DESC @@ -3259,13 +3260,12 @@ export class PgStore extends BasePgStore { ${ args.includeTxMetadata ? this.sql` - SELECT ${unsafeCols(this.sql, [ + SELECT ${this.sql([ 'nft.asset_identifier', 'nft.value', ...prefixedCols(TX_COLUMNS, 'txs'), - abiColumn(), 'nft.count', - ])} + ])}, ${abiColumn(this.sql)} FROM nft INNER JOIN txs USING (tx_id) WHERE txs.canonical = TRUE AND txs.microblock_canonical = TRUE @@ -3301,7 +3301,7 @@ export class PgStore extends BasePgStore { includeTxMetadata: boolean; }): Promise<{ results: NftEventWithTxMetadata[]; total: number }> { const columns = args.includeTxMetadata - ? unsafeCols(this.sql, [ + ? this.sql`${this.sql([ 'asset_identifier', 'value', 'event_index', @@ -3309,8 +3309,7 @@ export class PgStore extends BasePgStore { 'sender', 'recipient', ...prefixedCols(TX_COLUMNS, 'txs'), - abiColumn(), - ]) + ])}, ${abiColumn(this.sql)}` : this.sql`nft.*`; const nftTxResults = await this.sql<(DbNftEvent & ContractTxQueryResult & { count: number })[]>` SELECT ${columns}, (COUNT(*) OVER())::INTEGER AS count @@ -3362,7 +3361,7 @@ export class PgStore extends BasePgStore { includeTxMetadata: boolean; }): Promise<{ results: NftEventWithTxMetadata[]; total: number }> { const columns = args.includeTxMetadata - ? unsafeCols(this.sql, [ + ? this.sql`${this.sql([ 'asset_identifier', 'value', 'event_index', @@ -3370,8 +3369,7 @@ export class PgStore extends BasePgStore { 'sender', 'recipient', ...prefixedCols(TX_COLUMNS, 'txs'), - abiColumn(), - ]) + ])}, ${abiColumn(this.sql)}` : this.sql`nft.*`; const nftTxResults = await this.sql<(DbNftEvent & ContractTxQueryResult & { count: number })[]>` SELECT ${columns}, (COUNT(*) OVER())::INTEGER AS count @@ -3442,7 +3440,7 @@ export class PgStore extends BasePgStore { return await this.sqlTransaction(async sql => { const maxBlockHeight = await this.getMaxBlockHeight(sql, { includeUnanchored }); const result = await sql` - SELECT ${unsafeCols(sql, [...TX_COLUMNS, abiColumn()])} + SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE tx_id IN ${sql(txIds)} AND block_height <= ${maxBlockHeight} From ae78773930c92819709c148933d3daae32f87d4c Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Tue, 18 Jun 2024 13:54:48 +0200 Subject: [PATCH 3/3] feat: tx ordering options (#2005) * feat: ordering options for `/extendex/v1/tx` endpoint * test: add tx ordering tests * docs: openapi docs for tx ordering options * ci: disable subnets test (not working) --- .github/workflows/ci.yml | 1 + docs/openapi.yaml | 18 +++ migrations/1718632097776_tx-sort-indexes.js | 11 ++ src/api/routes/tx.ts | 31 ++++ src/datastore/pg-store.ts | 27 +++- src/tests/tx-tests.ts | 152 ++++++++++++++++++++ 6 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 migrations/1718632097776_tx-sort-indexes.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19a6db61a6..39d80ecf6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -413,6 +413,7 @@ jobs: parallel: true test-subnets: + if: false runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/docs/openapi.yaml b/docs/openapi.yaml index d1ef93fbac..fb1f854746 100644 --- a/docs/openapi.yaml +++ b/docs/openapi.yaml @@ -219,6 +219,24 @@ paths: items: type: string enum: [coinbase, token_transfer, smart_contract, contract_call, poison_microblock, tenure_change] + - name: sort_by + in: query + description: Option to sort results by block height, timestamp, or fee + required: false + schema: + type: string + enum: [block_height, burn_block_time, fee] + example: burn_block_time + default: block_height + - name: order + in: query + description: Option to sort results in ascending or descending order + required: false + schema: + type: string + enum: [asc, desc] + example: desc + default: desc - name: unanchored in: query description: Include transaction data from unanchored (i.e. unconfirmed) microblocks diff --git a/migrations/1718632097776_tx-sort-indexes.js b/migrations/1718632097776_tx-sort-indexes.js new file mode 100644 index 0000000000..d5e8adb9f5 --- /dev/null +++ b/migrations/1718632097776_tx-sort-indexes.js @@ -0,0 +1,11 @@ +/** @param { import("node-pg-migrate").MigrationBuilder } pgm */ +exports.up = pgm => { + pgm.createIndex('txs', 'burn_block_time'); + pgm.createIndex('txs', 'fee_rate'); +}; + +/** @param { import("node-pg-migrate").MigrationBuilder } pgm */ +exports.down = pgm => { + pgm.dropIndex('txs', 'burn_block_time'); + pgm.dropIndex('txs', 'fee_rate'); +}; diff --git a/src/api/routes/tx.ts b/src/api/routes/tx.ts index 73eae43152..b20b87d979 100644 --- a/src/api/routes/tx.ts +++ b/src/api/routes/tx.ts @@ -65,12 +65,43 @@ export function createTxRouter(db: PgStore): express.Router { txTypeFilter = []; } + let order: 'asc' | 'desc' | undefined; + if (req.query.order) { + if ( + typeof req.query.order === 'string' && + (req.query.order === 'asc' || req.query.order === 'desc') + ) { + order = req.query.order; + } else { + throw new InvalidRequestError( + `The "order" query parameter must be a 'desc' or 'asc'`, + InvalidRequestErrorType.invalid_param + ); + } + } + + let sortBy: 'block_height' | 'burn_block_time' | 'fee' | undefined; + if (req.query.sort_by) { + if ( + typeof req.query.sort_by === 'string' && + ['block_height', 'burn_block_time', 'fee'].includes(req.query.sort_by) + ) { + sortBy = req.query.sort_by as typeof sortBy; + } else { + throw new InvalidRequestError( + `The "sort_by" query parameter must be 'block_height', 'burn_block_time', or 'fee'`, + InvalidRequestErrorType.invalid_param + ); + } + } const includeUnanchored = isUnanchoredRequest(req, res, next); const { results: txResults, total } = await db.getTxList({ offset, limit, txTypeFilter, includeUnanchored, + order, + sortBy, }); const results = txResults.map(tx => parseDbTx(tx)); const response: TransactionResults = { limit, offset, total, results }; diff --git a/src/datastore/pg-store.ts b/src/datastore/pg-store.ts index 3a95734b4b..726ddbf35c 100644 --- a/src/datastore/pg-store.ts +++ b/src/datastore/pg-store.ts @@ -102,6 +102,7 @@ import { import * as path from 'path'; import { PgStoreV2 } from './pg-store-v2'; import { MempoolOrderByParam, OrderParam } from '../api/query-helpers'; +import { Fragment } from 'postgres'; export const MIGRATIONS_DIR = path.join(REPO_DIR, 'migrations'); @@ -1415,16 +1416,38 @@ export class PgStore extends BasePgStore { offset, txTypeFilter, includeUnanchored, + order, + sortBy, }: { limit: number; offset: number; txTypeFilter: TransactionType[]; includeUnanchored: boolean; + order?: 'desc' | 'asc'; + sortBy?: 'block_height' | 'burn_block_time' | 'fee'; }): Promise<{ results: DbTx[]; total: number }> { let totalQuery: { count: number }[]; let resultQuery: ContractTxQueryResult[]; return await this.sqlTransaction(async sql => { const maxHeight = await this.getMaxBlockHeight(sql, { includeUnanchored }); + const orderSql = order === 'asc' ? sql`ASC` : sql`DESC`; + + let orderBySql: Fragment; + switch (sortBy) { + case undefined: + case 'block_height': + orderBySql = sql`ORDER BY block_height ${orderSql}, microblock_sequence ${orderSql}, tx_index ${orderSql}`; + break; + case 'burn_block_time': + orderBySql = sql`ORDER BY burn_block_time ${orderSql}, block_height ${orderSql}, microblock_sequence ${orderSql}, tx_index ${orderSql}`; + break; + case 'fee': + orderBySql = sql`ORDER BY fee_rate ${orderSql}, block_height ${orderSql}, microblock_sequence ${orderSql}, tx_index ${orderSql}`; + break; + default: + throw new Error(`Invalid sortBy param: ${sortBy}`); + } + if (txTypeFilter.length === 0) { totalQuery = await sql<{ count: number }[]>` SELECT ${includeUnanchored ? sql('tx_count_unanchored') : sql('tx_count')} AS count @@ -1434,7 +1457,7 @@ export class PgStore extends BasePgStore { SELECT ${sql(TX_COLUMNS)}, ${abiColumn(sql)} FROM txs WHERE canonical = true AND microblock_canonical = true AND block_height <= ${maxHeight} - ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC + ${orderBySql} LIMIT ${limit} OFFSET ${offset} `; @@ -1451,7 +1474,7 @@ export class PgStore extends BasePgStore { FROM txs WHERE canonical = true AND microblock_canonical = true AND type_id IN ${sql(txTypeIds)} AND block_height <= ${maxHeight} - ORDER BY block_height DESC, microblock_sequence DESC, tx_index DESC + ${orderBySql} LIMIT ${limit} OFFSET ${offset} `; diff --git a/src/tests/tx-tests.ts b/src/tests/tx-tests.ts index 4fcc7ef98c..1d85176d5b 100644 --- a/src/tests/tx-tests.ts +++ b/src/tests/tx-tests.ts @@ -1938,6 +1938,158 @@ describe('tx tests', () => { expect(JSON.parse(fetchTx.text)).toEqual(expectedResp); }); + test('tx list - order by', async () => { + const block1 = new TestBlockBuilder({ block_height: 1, index_block_hash: '0x01' }) + .addTx({ + tx_id: '0x1234', + fee_rate: 1n, + burn_block_time: 2, + }) + .build(); + + await db.update(block1); + + const block2 = new TestBlockBuilder({ + block_height: 2, + index_block_hash: '0x02', + parent_block_hash: block1.block.block_hash, + parent_index_block_hash: block1.block.index_block_hash, + }) + .addTx({ + tx_id: '0x2234', + fee_rate: 3n, + burn_block_time: 1, + }) + .build(); + await db.update(block2); + + const block3 = new TestBlockBuilder({ + block_height: 3, + index_block_hash: '0x03', + parent_block_hash: block2.block.block_hash, + parent_index_block_hash: block2.block.index_block_hash, + }) + .addTx({ + tx_id: '0x3234', + fee_rate: 2n, + burn_block_time: 3, + }) + .build(); + await db.update(block3); + + const txsReqAsc = await supertest(api.server).get(`/extended/v1/tx?order=asc`); + expect(txsReqAsc.status).toBe(200); + expect(txsReqAsc.body).toEqual( + expect.objectContaining({ + results: [ + expect.objectContaining({ + tx_id: block1.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block2.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block3.txs[0].tx.tx_id, + }), + ], + }) + ); + + const txsReqDesc = await supertest(api.server).get(`/extended/v1/tx?order=desc`); + expect(txsReqDesc.status).toBe(200); + expect(txsReqDesc.body).toEqual( + expect.objectContaining({ + results: [ + expect.objectContaining({ + tx_id: block3.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block2.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block1.txs[0].tx.tx_id, + }), + ], + }) + ); + + const txsReqTimeDesc = await supertest(api.server).get( + `/extended/v1/tx?sort_by=burn_block_time&order=desc` + ); + expect(txsReqTimeDesc.status).toBe(200); + expect(txsReqTimeDesc.body).toEqual( + expect.objectContaining({ + results: [ + expect.objectContaining({ + tx_id: block3.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block1.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block2.txs[0].tx.tx_id, + }), + ], + }) + ); + + const txsReqTimeAsc = await supertest(api.server).get( + `/extended/v1/tx?sort_by=burn_block_time&order=asc` + ); + expect(txsReqTimeAsc.status).toBe(200); + expect(txsReqTimeAsc.body).toEqual( + expect.objectContaining({ + results: [ + expect.objectContaining({ + tx_id: block2.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block1.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block3.txs[0].tx.tx_id, + }), + ], + }) + ); + + const txsReqFeeDesc = await supertest(api.server).get(`/extended/v1/tx?sort_by=fee&order=desc`); + expect(txsReqFeeDesc.status).toBe(200); + expect(txsReqFeeDesc.body).toEqual( + expect.objectContaining({ + results: [ + expect.objectContaining({ + tx_id: block2.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block3.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block1.txs[0].tx.tx_id, + }), + ], + }) + ); + + const txsReqFeeAsc = await supertest(api.server).get(`/extended/v1/tx?sort_by=fee&order=asc`); + expect(txsReqFeeAsc.status).toBe(200); + expect(txsReqFeeAsc.body).toEqual( + expect.objectContaining({ + results: [ + expect.objectContaining({ + tx_id: block1.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block3.txs[0].tx.tx_id, + }), + expect.objectContaining({ + tx_id: block2.txs[0].tx.tx_id, + }), + ], + }) + ); + }); + test('fetch raw tx', async () => { const block: DbBlock = { block_hash: '0x1234',