fix(rosetta): use coinbase txs hash instead of stx_lock for forged unlock_transaction #760

This commit is contained in:
Asim Mehmood
2021-09-16 14:38:42 +05:00
committed by GitHub
parent 5f07d7455f
commit 37adcc70aa
4 changed files with 63 additions and 21 deletions

View File

@@ -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 };
}

View File

@@ -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);
});

View File

@@ -110,7 +110,8 @@ export async function getOperations(
tx: DbTx | DbMempoolTx | BaseTx,
db: DataStore,
minerRewards?: DbMinerReward[],
events?: DbEvent[]
events?: DbEvent[],
stxUnlockEvents?: StxUnlockEvent[]
): Promise<RosettaOperation[]> {
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));

View File

@@ -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() => {