From 09a8001892079b25cb3d6bfed03fcaf76fdee4f3 Mon Sep 17 00:00:00 2001 From: Rafael Cardenas Date: Mon, 12 May 2025 07:58:33 -0600 Subject: [PATCH 1/6] fix: rename contract_abi to contract_interface --- src/event-stream/core-node-message.ts | 2 +- src/event-stream/event-server.ts | 2 +- tests/api/address.test.ts | 2 +- tests/api/tx.test.ts | 16 ++++++++-------- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/event-stream/core-node-message.ts b/src/event-stream/core-node-message.ts index 9c8b276de..0f113d027 100644 --- a/src/event-stream/core-node-message.ts +++ b/src/event-stream/core-node-message.ts @@ -240,7 +240,7 @@ export interface CoreNodeTxMessage { raw_result: string; txid: string; tx_index: number; - contract_abi: ClarityAbi | null; + contract_interface: ClarityAbi | null; execution_cost: CoreNodeExecutionCostMessage; microblock_sequence: number | null; microblock_hash: string | null; diff --git a/src/event-stream/event-server.ts b/src/event-stream/event-server.ts index b3ab6dbff..86fe1b8e9 100644 --- a/src/event-stream/event-server.ts +++ b/src/event-stream/event-server.ts @@ -287,7 +287,7 @@ function parseDataStoreTxEventData( block_height: blockData.block_height, clarity_version: clarityVersion, source_code: tx.parsed_tx.payload.code_body, - abi: JSON.stringify(tx.core_tx.contract_abi), + abi: JSON.stringify(tx.core_tx.contract_interface), canonical: true, }); break; diff --git a/tests/api/address.test.ts b/tests/api/address.test.ts index 216e50595..6a93e827a 100644 --- a/tests/api/address.test.ts +++ b/tests/api/address.test.ts @@ -2514,7 +2514,7 @@ describe('address tests', () => { raw_result: '0x0100000000000000000000000000000001', // u1 txid: '0x' + txBuilder.txid(), tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, diff --git a/tests/api/tx.test.ts b/tests/api/tx.test.ts index 9913bc49d..e8f2372a4 100644 --- a/tests/api/tx.test.ts +++ b/tests/api/tx.test.ts @@ -384,7 +384,7 @@ describe('tx tests', () => { raw_result: '0x0100000000000000000000000000000001', // u1 txid: tx.tx_id, tx_index: 2, - contract_abi: abiSample, + contract_interface: abiSample, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, @@ -543,7 +543,7 @@ describe('tx tests', () => { raw_result: '0x0100000000000000000000000000000001', // u1 txid: tx.tx_id, tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, @@ -692,7 +692,7 @@ describe('tx tests', () => { raw_result: '0x0100000000000000000000000000000001', // u1 txid: tx.tx_id, tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, @@ -852,7 +852,7 @@ describe('tx tests', () => { raw_result: '0x0100000000000000000000000000000001', // u1 txid: '0x' + txBuilder.txid(), tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, @@ -1076,7 +1076,7 @@ describe('tx tests', () => { raw_result: '0x0100000000000000000000000000000001', // u1 txid: '0x' + txBuilder.txid(), tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, @@ -1480,7 +1480,7 @@ describe('tx tests', () => { raw_result: '0x0100000000000000000000000000000001', // u1 txid: '0x' + txBuilder.txid(), tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, @@ -1713,7 +1713,7 @@ describe('tx tests', () => { status: 'abort_by_response', txid: '0x' + txBuilder.txid(), tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, @@ -1869,7 +1869,7 @@ describe('tx tests', () => { status: 'abort_by_post_condition', txid: '0x' + txBuilder.txid(), tx_index: 2, - contract_abi: null, + contract_interface: null, microblock_hash: null, microblock_parent_hash: null, microblock_sequence: null, From c9e3ee20492074b81c574de4ffdb6a9b9419c143 Mon Sep 17 00:00:00 2001 From: janniks Date: Fri, 16 May 2025 16:11:44 +0200 Subject: [PATCH 2/6] fix: add deprecated .contract_abi field for replays --- src/event-stream/core-node-message.ts | 2 ++ src/event-stream/event-server.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/event-stream/core-node-message.ts b/src/event-stream/core-node-message.ts index 0f113d027..099a193b3 100644 --- a/src/event-stream/core-node-message.ts +++ b/src/event-stream/core-node-message.ts @@ -241,6 +241,8 @@ export interface CoreNodeTxMessage { txid: string; tx_index: number; contract_interface: ClarityAbi | null; + /** @deprecated Use `contract_interface` instead. The node renamed `contract_abi` to `contract_interface`. */ + contract_abi: ClarityAbi | null; execution_cost: CoreNodeExecutionCostMessage; microblock_sequence: number | null; microblock_hash: string | null; diff --git a/src/event-stream/event-server.ts b/src/event-stream/event-server.ts index 86fe1b8e9..eb0073451 100644 --- a/src/event-stream/event-server.ts +++ b/src/event-stream/event-server.ts @@ -287,7 +287,7 @@ function parseDataStoreTxEventData( block_height: blockData.block_height, clarity_version: clarityVersion, source_code: tx.parsed_tx.payload.code_body, - abi: JSON.stringify(tx.core_tx.contract_interface), + abi: JSON.stringify(tx.core_tx.contract_interface ?? tx.core_tx.contract_abi), canonical: true, }); break; From 1f5bc9c7119d8a3208c97fa66b43e68df40c4a61 Mon Sep 17 00:00:00 2001 From: janniks Date: Fri, 16 May 2025 16:12:01 +0200 Subject: [PATCH 3/6] chore: add patch script for missing abi values --- scripts/patch-missing-contract-abis.ts | 121 +++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 scripts/patch-missing-contract-abis.ts diff --git a/scripts/patch-missing-contract-abis.ts b/scripts/patch-missing-contract-abis.ts new file mode 100644 index 000000000..974e1d28f --- /dev/null +++ b/scripts/patch-missing-contract-abis.ts @@ -0,0 +1,121 @@ +import { connectPostgres, PgSqlClient } from '@hirosystems/api-toolkit'; +import { StacksCoreRpcClient } from '../src/core-rpc/client'; +import { getConnectionArgs, getConnectionConfig } from '../src/datastore/connection'; +import { ClarityAbi } from '../src/event-stream/contract-abi'; +import { loadDotEnv } from '../src/helpers'; + +const BATCH_SIZE = 64; +const LAST_BLOCK_HEIGHT = parseInt(process.env.LAST_BLOCK_HEIGHT ?? '-1'); + +// 1) Environment + DB Setup +loadDotEnv(); +const sql: PgSqlClient = await connectPostgres({ + usageName: 'patch-missing-contract-abis', + connectionArgs: getConnectionArgs(), + connectionConfig: getConnectionConfig(), +}); + +try { + console.log('Starting script to patch missing contract ABIs...'); + + // 2) Initialize script variables and RPC client + let lastBlockHeight = LAST_BLOCK_HEIGHT; // Initial value for the first query + + let totalConsideredCount = 0; + let totalPatchedCount = 0; + + const rpc = new StacksCoreRpcClient(); // Default to RPC host from ENV + + // 3) Main processing loop: Fetch and patch contracts in batches + while (true) { + // 3.1) Find contracts whose ABI is still missing (paginated) + const missing = await sql<{ contract_id: string; block_height: number }[]>` + SELECT contract_id, block_height + FROM smart_contracts + WHERE (abi::text = '"null"') + AND canonical = TRUE + AND block_height > ${lastBlockHeight} + ORDER BY block_height ASC + LIMIT ${BATCH_SIZE} + `; + + if (missing.length === 0) { + if (totalConsideredCount === 0) { + console.log(' - No contracts with missing ABI found.'); + } else { + console.log(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`); + } + break; // Exit the while loop + } + + console.log(`- Found batch of ${missing.length} contracts with missing ABIs.`); + + // 3.2) Process each contract in the current batch + for (const contract of missing) { + totalConsideredCount++; + const { contract_id, block_height } = contract; + const [address, name] = contract_id.split('.'); + if (!address || !name) { + console.warn(` - Skipping invalid contract id: ${contract_id}`); + continue; + } + + try { + // 3.3) Fetch ABI from the connected Stacks node + const abi = await rpc.fetchJson(`v2/contracts/interface/${address}/${name}`); + + if (!abi || typeof abi !== 'object' || Object.keys(abi).length === 0) { + console.warn(` - Skipping ${contract_id}. Fetched empty or invalid ABI.`); + continue; + } + + // 3.4) Update row for this contract still missing an ABI + const rows = await sql` + UPDATE smart_contracts + SET abi = ${abi} + WHERE contract_id = ${contract_id} + AND (abi::text = '"null"') + AND canonical = TRUE + `; + if (rows.count === 0) { + console.warn(` - Failed to patch ${contract_id}. No rows updated.`); + continue; + } + console.log(` - Patched ABI for ${contract_id}`); + totalPatchedCount++; + } catch (err: any) { + let errorMessage = 'Unknown error'; + if (err instanceof Error) { + errorMessage = err.message; + } else if (typeof err === 'string') { + errorMessage = err; + } else if (err && typeof err.message === 'string') { + errorMessage = err.message; + } + console.error(` - Failed to patch ${contract_id}:`, errorMessage); + } + + // Keep track of the latest block_height we've processed + if (block_height > lastBlockHeight) { + lastBlockHeight = block_height; + console.log(` - Processed up to block ${lastBlockHeight}`); + } + } + + // 3.5) Check if it was the last batch + if (missing.length < BATCH_SIZE) { + console.log(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`); + break; // Last batch was smaller than batchSize, so no more items. + } + } +} catch (err: any) { + console.error('An unexpected error occurred:', err); + if (err instanceof Error && err.stack) console.error('Stack trace:', err.stack); + if (err instanceof Error && err.message) console.error('Error message:', err.message); + process.exit(1); // Exit with error if an unhandled exception occurs in the main block +} finally { + // 4) Close DB connection + console.log('Closing database connection...'); + await sql.end({ timeout: 5 }); + console.log('Done.'); +} From e7333691a9992acd5fcac109b38f34268691bd58 Mon Sep 17 00:00:00 2001 From: janniks Date: Fri, 16 May 2025 17:07:49 +0200 Subject: [PATCH 4/6] chore: make deprecated field optional --- src/event-stream/core-node-message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event-stream/core-node-message.ts b/src/event-stream/core-node-message.ts index 099a193b3..e6e933581 100644 --- a/src/event-stream/core-node-message.ts +++ b/src/event-stream/core-node-message.ts @@ -242,7 +242,7 @@ export interface CoreNodeTxMessage { tx_index: number; contract_interface: ClarityAbi | null; /** @deprecated Use `contract_interface` instead. The node renamed `contract_abi` to `contract_interface`. */ - contract_abi: ClarityAbi | null; + contract_abi: ClarityAbi | null | undefined; execution_cost: CoreNodeExecutionCostMessage; microblock_sequence: number | null; microblock_hash: string | null; From 9e9efbfb03831f8bb7b7b96f65ada2a3051b6d3e Mon Sep 17 00:00:00 2001 From: janniks Date: Fri, 16 May 2025 17:51:53 +0200 Subject: [PATCH 5/6] fix: make .contract_abi field optional instead of undefined --- src/event-stream/core-node-message.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/event-stream/core-node-message.ts b/src/event-stream/core-node-message.ts index e6e933581..065d1dd87 100644 --- a/src/event-stream/core-node-message.ts +++ b/src/event-stream/core-node-message.ts @@ -242,7 +242,7 @@ export interface CoreNodeTxMessage { tx_index: number; contract_interface: ClarityAbi | null; /** @deprecated Use `contract_interface` instead. The node renamed `contract_abi` to `contract_interface`. */ - contract_abi: ClarityAbi | null | undefined; + contract_abi?: ClarityAbi | null; execution_cost: CoreNodeExecutionCostMessage; microblock_sequence: number | null; microblock_hash: string | null; From 5da904752283ef70103f4356d44fea414018ece3 Mon Sep 17 00:00:00 2001 From: janniks Date: Mon, 19 May 2025 16:55:34 +0200 Subject: [PATCH 6/6] refactor: switch console logs to logger --- scripts/patch-missing-contract-abis.ts | 41 ++++++++++---------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/scripts/patch-missing-contract-abis.ts b/scripts/patch-missing-contract-abis.ts index 974e1d28f..258c71b9c 100644 --- a/scripts/patch-missing-contract-abis.ts +++ b/scripts/patch-missing-contract-abis.ts @@ -3,6 +3,7 @@ import { StacksCoreRpcClient } from '../src/core-rpc/client'; import { getConnectionArgs, getConnectionConfig } from '../src/datastore/connection'; import { ClarityAbi } from '../src/event-stream/contract-abi'; import { loadDotEnv } from '../src/helpers'; +import { logger } from '../src/logger'; const BATCH_SIZE = 64; const LAST_BLOCK_HEIGHT = parseInt(process.env.LAST_BLOCK_HEIGHT ?? '-1'); @@ -16,7 +17,7 @@ const sql: PgSqlClient = await connectPostgres({ }); try { - console.log('Starting script to patch missing contract ABIs...'); + logger.info('Starting script to patch missing contract ABIs...'); // 2) Initialize script variables and RPC client let lastBlockHeight = LAST_BLOCK_HEIGHT; // Initial value for the first query @@ -41,14 +42,14 @@ try { if (missing.length === 0) { if (totalConsideredCount === 0) { - console.log(' - No contracts with missing ABI found.'); + logger.info(' - No contracts with missing ABI found.'); } else { - console.log(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`); + logger.info(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`); } break; // Exit the while loop } - console.log(`- Found batch of ${missing.length} contracts with missing ABIs.`); + logger.info(`- Found batch of ${missing.length} contracts with missing ABIs.`); // 3.2) Process each contract in the current batch for (const contract of missing) { @@ -56,7 +57,7 @@ try { const { contract_id, block_height } = contract; const [address, name] = contract_id.split('.'); if (!address || !name) { - console.warn(` - Skipping invalid contract id: ${contract_id}`); + logger.warn(` - Skipping invalid contract id: ${contract_id}`); continue; } @@ -65,7 +66,7 @@ try { const abi = await rpc.fetchJson(`v2/contracts/interface/${address}/${name}`); if (!abi || typeof abi !== 'object' || Object.keys(abi).length === 0) { - console.warn(` - Skipping ${contract_id}. Fetched empty or invalid ABI.`); + logger.warn(` - Skipping ${contract_id}. Fetched empty or invalid ABI.`); continue; } @@ -78,44 +79,34 @@ try { AND canonical = TRUE `; if (rows.count === 0) { - console.warn(` - Failed to patch ${contract_id}. No rows updated.`); + logger.warn(` - Failed to patch ${contract_id}. No rows updated.`); continue; } - console.log(` - Patched ABI for ${contract_id}`); + logger.info(` - Patched ABI for ${contract_id}`); totalPatchedCount++; } catch (err: any) { - let errorMessage = 'Unknown error'; - if (err instanceof Error) { - errorMessage = err.message; - } else if (typeof err === 'string') { - errorMessage = err; - } else if (err && typeof err.message === 'string') { - errorMessage = err.message; - } - console.error(` - Failed to patch ${contract_id}:`, errorMessage); + logger.error(err, ` - Failed to patch ${contract_id}`); } // Keep track of the latest block_height we've processed if (block_height > lastBlockHeight) { lastBlockHeight = block_height; - console.log(` - Processed up to block ${lastBlockHeight}`); + logger.info(` - Processed up to block ${lastBlockHeight}`); } } // 3.5) Check if it was the last batch if (missing.length < BATCH_SIZE) { - console.log(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`); + logger.info(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`); break; // Last batch was smaller than batchSize, so no more items. } } } catch (err: any) { - console.error('An unexpected error occurred:', err); - if (err instanceof Error && err.stack) console.error('Stack trace:', err.stack); - if (err instanceof Error && err.message) console.error('Error message:', err.message); - process.exit(1); // Exit with error if an unhandled exception occurs in the main block + logger.error(err, 'An unexpected error occurred'); + throw err; } finally { // 4) Close DB connection - console.log('Closing database connection...'); + logger.info('Closing database connection...'); await sql.end({ timeout: 5 }); - console.log('Done.'); + logger.info('Done.'); }