From ce9b38f051216d149375d64b3dfb90a75ab50fcd Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Mon, 1 Jan 2024 14:39:25 +0100 Subject: [PATCH] fix: handle `Problematic` status in `/drop_mempool_tx` event (#1790) --- .../transaction-status.schema.json | 2 +- docs/generated.d.ts | 3 +- src/api/controllers/db-controller.ts | 2 + src/datastore/common.ts | 2 + src/datastore/helpers.ts | 2 + src/datastore/pg-store.ts | 1 + src/event-stream/core-node-message.ts | 3 +- src/tests/mempool-tests.ts | 42 ++++++++++++++++++- 8 files changed, 52 insertions(+), 5 deletions(-) diff --git a/docs/entities/mempool-transactions/transaction-status.schema.json b/docs/entities/mempool-transactions/transaction-status.schema.json index 660609ca..e19bfed1 100644 --- a/docs/entities/mempool-transactions/transaction-status.schema.json +++ b/docs/entities/mempool-transactions/transaction-status.schema.json @@ -2,5 +2,5 @@ "title": "MempoolTransactionStatus", "description": "Status of the transaction", "type": "string", - "enum": ["pending", "dropped_replace_by_fee", "dropped_replace_across_fork", "dropped_too_expensive", "dropped_stale_garbage_collect"] + "enum": ["pending", "dropped_replace_by_fee", "dropped_replace_across_fork", "dropped_too_expensive", "dropped_stale_garbage_collect", "dropped_problematic"] } diff --git a/docs/generated.d.ts b/docs/generated.d.ts index 933fe0dc..9af2ccf6 100644 --- a/docs/generated.d.ts +++ b/docs/generated.d.ts @@ -569,7 +569,8 @@ export type MempoolTransactionStatus = | "dropped_replace_by_fee" | "dropped_replace_across_fork" | "dropped_too_expensive" - | "dropped_stale_garbage_collect"; + | "dropped_stale_garbage_collect" + | "dropped_problematic"; /** * Describes representation of a Type-1 Stacks 2.0 transaction. https://github.com/blockstack/stacks-blockchain/blob/master/sip/sip-005-blocks-and-transactions.md#type-1-instantiating-a-smart-contract */ diff --git a/src/api/controllers/db-controller.ts b/src/api/controllers/db-controller.ts index eecf271f..8ca8da73 100644 --- a/src/api/controllers/db-controller.ts +++ b/src/api/controllers/db-controller.ts @@ -161,6 +161,8 @@ function getTxStatusString(txStatus: DbTxStatus): TransactionStatus | MempoolTra return 'dropped_replace_across_fork'; case DbTxStatus.DroppedTooExpensive: return 'dropped_too_expensive'; + case DbTxStatus.DroppedProblematic: + return 'dropped_problematic'; case DbTxStatus.DroppedStaleGarbageCollect: case DbTxStatus.DroppedApiGarbageCollect: return 'dropped_stale_garbage_collect'; diff --git a/src/datastore/common.ts b/src/datastore/common.ts index 981fa6a7..da919139 100644 --- a/src/datastore/common.ts +++ b/src/datastore/common.ts @@ -117,6 +117,8 @@ export enum DbTxStatus { DroppedStaleGarbageCollect = -13, /** Dropped by the API (even though the Stacks node hadn't dropped it) because it exceeded maximum mempool age */ DroppedApiGarbageCollect = -14, + /** Transaction is problematic (e.g. a DDoS vector) and should be dropped. */ + DroppedProblematic = -15, } export enum DbTxAnchorMode { diff --git a/src/datastore/helpers.ts b/src/datastore/helpers.ts index e35cabe9..db41e486 100644 --- a/src/datastore/helpers.ts +++ b/src/datastore/helpers.ts @@ -990,6 +990,8 @@ export function getTxDbStatus( return DbTxStatus.DroppedTooExpensive; case 'StaleGarbageCollect': return DbTxStatus.DroppedStaleGarbageCollect; + case 'Problematic': + return DbTxStatus.DroppedProblematic; default: throw new Error(`Unexpected tx status: ${txCoreStatus}`); } diff --git a/src/datastore/pg-store.ts b/src/datastore/pg-store.ts index 22ed8861..3adaf61d 100644 --- a/src/datastore/pg-store.ts +++ b/src/datastore/pg-store.ts @@ -1247,6 +1247,7 @@ export class PgStore extends BasePgStore { DbTxStatus.DroppedTooExpensive, DbTxStatus.DroppedStaleGarbageCollect, DbTxStatus.DroppedApiGarbageCollect, + DbTxStatus.DroppedProblematic, ]; const resultQuery = await sql<(MempoolTxQueryResult & { count: number })[]>` SELECT ${unsafeCols(sql, [ diff --git a/src/event-stream/core-node-message.ts b/src/event-stream/core-node-message.ts index dd5927a0..7e0dea7a 100644 --- a/src/event-stream/core-node-message.ts +++ b/src/event-stream/core-node-message.ts @@ -301,7 +301,8 @@ export type CoreNodeDropMempoolTxReasonType = | 'ReplaceByFee' | 'ReplaceAcrossFork' | 'TooExpensive' - | 'StaleGarbageCollect'; + | 'StaleGarbageCollect' + | 'Problematic'; export interface CoreNodeDropMempoolTxMessage { dropped_txids: string[]; diff --git a/src/tests/mempool-tests.ts b/src/tests/mempool-tests.ts index fd33072c..3948d9b6 100644 --- a/src/tests/mempool-tests.ts +++ b/src/tests/mempool-tests.ts @@ -323,9 +323,14 @@ describe('mempool tests', () => { tx_id: '0x8912000000000000000000000000000000000000000000000000000000000005', receipt_time: 1594307705, }; + const mempoolTx6: DbMempoolTxRaw = { + ...mempoolTx1, + tx_id: '0x8912000000000000000000000000000000000000000000000000000000000006', + receipt_time: 1594307706, + }; await db.updateMempoolTxs({ - mempoolTxs: [mempoolTx1, mempoolTx2, mempoolTx3, mempoolTx4, mempoolTx5], + mempoolTxs: [mempoolTx1, mempoolTx2, mempoolTx3, mempoolTx4, mempoolTx5, mempoolTx6], }); await db.dropMempoolTxs({ status: DbTxStatus.DroppedReplaceAcrossFork, @@ -450,6 +455,31 @@ describe('mempool tests', () => { }; expect(JSON.parse(searchResult5.text)).toEqual(expectedResp5); + await db.dropMempoolTxs({ + status: DbTxStatus.DroppedProblematic, + txIds: [mempoolTx6.tx_id], + }); + const searchResult6 = await supertest(api.server).get(`/extended/v1/tx/${mempoolTx6.tx_id}`); + expect(searchResult6.status).toBe(200); + expect(searchResult6.type).toBe('application/json'); + const expectedResp6 = { + tx_id: '0x8912000000000000000000000000000000000000000000000000000000000006', + tx_status: 'dropped_problematic', + tx_type: 'coinbase', + fee_rate: '1234', + nonce: 0, + anchor_mode: 'any', + sender_address: 'sender-addr', + sponsor_address: 'sponsor-addr', + sponsored: true, + post_condition_mode: 'allow', + post_conditions: [], + receipt_time: 1594307706, + receipt_time_iso: '2020-07-09T15:15:06.000Z', + coinbase_payload: { data: '0x636f696e62617365206869', alt_recipient: null }, + }; + expect(JSON.parse(searchResult6.text)).toEqual(expectedResp6); + const mempoolDroppedResult1 = await supertest(api.server).get( '/extended/v1/tx/mempool/dropped' ); @@ -458,6 +488,10 @@ describe('mempool tests', () => { expect(mempoolDroppedResult1.body).toEqual( expect.objectContaining({ results: expect.arrayContaining([ + expect.objectContaining({ + tx_id: '0x8912000000000000000000000000000000000000000000000000000000000006', + tx_status: 'dropped_problematic', + }), expect.objectContaining({ tx_id: '0x8912000000000000000000000000000000000000000000000000000000000005', tx_status: 'dropped_stale_garbage_collect', @@ -549,10 +583,14 @@ describe('mempool tests', () => { ); expect(mempoolDroppedResult2.status).toBe(200); expect(mempoolDroppedResult2.type).toBe('application/json'); - expect(mempoolDroppedResult2.body.results).toHaveLength(4); + expect(mempoolDroppedResult2.body.results).toHaveLength(5); expect(mempoolDroppedResult2.body).toEqual( expect.objectContaining({ results: expect.arrayContaining([ + expect.objectContaining({ + tx_id: '0x8912000000000000000000000000000000000000000000000000000000000006', + tx_status: 'dropped_problematic', + }), expect.objectContaining({ tx_id: '0x8912000000000000000000000000000000000000000000000000000000000005', tx_status: 'dropped_stale_garbage_collect',