From 37adcc70aa55e58c9ad3dd0684b24972130fa6d4 Mon Sep 17 00:00:00 2001 From: Asim Mehmood Date: Thu, 16 Sep 2021 14:38:42 +0500 Subject: [PATCH] fix(rosetta): use coinbase txs hash instead of stx_lock for forged unlock_transaction #760 --- src/api/controllers/db-controller.ts | 16 ++--------- src/datastore/postgres-store.ts | 21 +++++++++++--- src/rosetta-helpers.ts | 6 +++- src/tests-rosetta/api.ts | 41 ++++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 21 deletions(-) diff --git a/src/api/controllers/db-controller.ts b/src/api/controllers/db-controller.ts index 9d06f352..fb7c432c 100644 --- a/src/api/controllers/db-controller.ts +++ b/src/api/controllers/db-controller.ts @@ -489,6 +489,8 @@ export async function getRosettaBlockTransactionsFromDataStore(opts: { return { found: false }; } + const unlockingEvents = await opts.db.getUnlockedAddressesAtBlock(blockQuery.result); + const transactions: RosettaTransaction[] = []; for (const tx of txsQuery.result) { @@ -503,8 +505,7 @@ export async function getRosettaBlockTransactionsFromDataStore(opts: { }); events = eventsQuery.results; } - - const operations = await getOperations(tx, opts.db, minerRewards, events); + const operations = await getOperations(tx, opts.db, minerRewards, events, unlockingEvents); const txMemo = parseTransactionMemo(tx); const rosettaTx: RosettaTransaction = { transaction_identifier: { hash: tx.tx_id }, @@ -518,17 +519,6 @@ export async function getRosettaBlockTransactionsFromDataStore(opts: { transactions.push(rosettaTx); } - // Search for unlocking events - const unlockingEvents = await opts.db.getUnlockedAddressesAtBlock(blockQuery.result); - if (unlockingEvents.length > 0) { - const operations: RosettaOperation[] = []; - processUnlockingEvents(unlockingEvents, operations); - transactions.push({ - transaction_identifier: { hash: unlockingEvents[0].tx_id }, // All unlocking events share the same tx_id - operations: operations, - }); - } - return { found: true, result: transactions }; } diff --git a/src/datastore/postgres-store.ts b/src/datastore/postgres-store.ts index 72df58f4..ade0dd4a 100644 --- a/src/datastore/postgres-store.ts +++ b/src/datastore/postgres-store.ts @@ -5855,25 +5855,38 @@ export class PgDataStore const lockQuery = await client.query<{ locked_amount: string; unlock_height: string; - block_height: string; locked_address: string; tx_id: Buffer; }>( ` - SELECT locked_amount, unlock_height, block_height, tx_id, locked_address + SELECT locked_amount, unlock_height, locked_address FROM stx_lock_events - WHERE canonical = true AND unlock_height <= $1 AND unlock_height > $2 + WHERE microblock_canonical = true AND canonical = true + AND unlock_height <= $1 AND unlock_height > $2 `, [current_burn_height, previous_burn_height] ); + const txIdQuery = await client.query<{ + tx_id: Buffer; + }>( + ` + SELECT tx_id + FROM txs + WHERE microblock_canonical = true AND canonical = true + AND block_height = $1 AND type_id = $2 + LIMIT 1 + `, + [block.block_height, DbTxTypeId.Coinbase] + ); + const result: StxUnlockEvent[] = []; lockQuery.rows.forEach(row => { const unlockEvent: StxUnlockEvent = { unlock_height: row.unlock_height, unlocked_amount: row.locked_amount, stacker_address: row.locked_address, - tx_id: bufferToHexPrefixString(row.tx_id), + tx_id: bufferToHexPrefixString(txIdQuery.rows[0].tx_id), }; result.push(unlockEvent); }); diff --git a/src/rosetta-helpers.ts b/src/rosetta-helpers.ts index 2754ae65..7d74e4c5 100644 --- a/src/rosetta-helpers.ts +++ b/src/rosetta-helpers.ts @@ -110,7 +110,8 @@ export async function getOperations( tx: DbTx | DbMempoolTx | BaseTx, db: DataStore, minerRewards?: DbMinerReward[], - events?: DbEvent[] + events?: DbEvent[], + stxUnlockEvents?: StxUnlockEvent[] ): Promise { const operations: RosettaOperation[] = []; const txType = getTxTypeString(tx.type_id); @@ -133,6 +134,9 @@ export async function getOperations( if (minerRewards !== undefined) { getMinerOperations(minerRewards, operations); } + if (stxUnlockEvents && stxUnlockEvents?.length > 0) { + processUnlockingEvents(stxUnlockEvents, operations); + } break; case 'poison_microblock': operations.push(makePoisonMicroblockOperation(tx, 0)); diff --git a/src/tests-rosetta/api.ts b/src/tests-rosetta/api.ts index 02df4dc8..2938248c 100644 --- a/src/tests-rosetta/api.ts +++ b/src/tests-rosetta/api.ts @@ -74,7 +74,6 @@ import { makeSigHashPreSign, MessageSignature } from '@stacks/transactions'; import { decodeBtcAddress } from '@stacks/stacking'; - describe('Rosetta API', () => { let db: PgDataStore; let client: PoolClient; @@ -2618,7 +2617,7 @@ describe('Rosetta API', () => { expect(blockStxOpsQuery.status).toBe(200); expect(blockStxOpsQuery.type).toBe('application/json'); - let stxUnlockHeight = blockStxOpsQuery.body.block.transactions[1].operations[1].metadata.unlock_burn_height; + let stxUnlockHeight = Number.parseInt(blockStxOpsQuery.body.block.transactions[1].operations[1].metadata.unlock_burn_height); expect(stxUnlockHeight).toBeTruthy(); const blockTxOpsQuery = await supertest(api.address) @@ -2671,6 +2670,7 @@ describe('Rosetta API', () => { expect(blockTxOpsQuery.body.operations).toContainEqual(expect.objectContaining(expectedStxLockOp)); let current_burn_block_height = block.result.burn_block_height; + //wait for the unlock block height while(current_burn_block_height < stxUnlockHeight){ block = await db.getCurrentBlock(); assert(block.found); @@ -2684,9 +2684,44 @@ describe('Rosetta API', () => { network_identifier: { blockchain: 'stacks', network: 'testnet' }, block_identifier: { hash: block.result.block_hash }, }); + expect(query1.status).toBe(200); expect(query1.type).toBe('application/json'); - expect(JSON.stringify(query1.body)).toMatch(/"stx_unlock"/) + expect(query1.body.block.transactions[0].operations[0].type).toBe('coinbase'); + expect(query1.body.block.transactions[0].operations[1].type).toBe('stx_unlock'); + + const query2 = await supertest(api.address) + .post(`/rosetta/v1/block/transaction`) + .send({ + network_identifier: { blockchain: 'stacks', network: 'testnet' }, + block_identifier: { hash: block.result.block_hash }, + transaction_identifier: {hash: query1.body.block.transactions[0].transaction_identifier.hash } + }); + + const expectedResponse = { + operation_identifier: { + index: 1, + }, + type: "stx_unlock", + status: "success", + account: { + address: testnetKeys[0].stacksAddress, + }, + amount: { + value: stacking_amount, + currency: { + decimals: 6, + symbol: "STX" + } + }, + metadata: { + tx_id: query1.body.block.transactions[0].transaction_identifier.hash, + } + } + expect(query2.status).toBe(200); + expect(query2.type).toBe('application/json'); + expect(query2.body.operations[1]).toStrictEqual(expectedResponse); + }) test('delegate-stacking rosetta transaction cycle', async() => {