mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-01-12 22:43:34 +08:00
fix(rosetta): use coinbase txs hash instead of stx_lock for forged unlock_transaction #760
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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() => {
|
||||
|
||||
Reference in New Issue
Block a user