Skip to content

Commit 82f3fea

Browse files
authored
fix: adjust contract ABI writes to read from contract_interface key instead of contract_abi (#2276)
1 parent a0cd9f2 commit 82f3fea

File tree

6 files changed

+146
-12
lines changed

6 files changed

+146
-12
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@
5959
"devenv:stop:pg": "docker compose -f docker/docker-compose.dev.postgres.yml down -v -t 0",
6060
"devenv:logs": "docker compose -f docker/docker-compose.dev.postgres.yml -f docker/docker-compose.dev.stacks-blockchain.yml -f docker/docker-compose.dev.bitcoind.yml logs -t -f",
6161
"devenv:logs:subnets": "docker compose -f docker/docker-compose.dev.postgres.yml -f docker/docker-compose.dev.subnets.yml logs -t -f",
62-
"devenv:logs-krypton": "docker compose -f docker/docker-compose.dev.postgres.yml -f docker/docker-compose.dev.stacks-krypton.yml logs -t -f"
62+
"devenv:logs-krypton": "docker compose -f docker/docker-compose.dev.postgres.yml -f docker/docker-compose.dev.stacks-krypton.yml logs -t -f",
63+
"patch:populate-missing-contract-abis": "node ./lib/patches/populate-missing-contract-abis.js"
6364
},
6465
"repository": {
6566
"type": "git",

src/event-stream/core-node-message.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,9 @@ export interface CoreNodeTxMessage {
240240
raw_result: string;
241241
txid: string;
242242
tx_index: number;
243-
contract_abi: ClarityAbi | null;
243+
contract_interface: ClarityAbi | null;
244+
/** @deprecated Use `contract_interface` instead. The node renamed `contract_abi` to `contract_interface`. */
245+
contract_abi?: ClarityAbi | null;
244246
execution_cost: CoreNodeExecutionCostMessage;
245247
microblock_sequence: number | null;
246248
microblock_hash: string | null;

src/event-stream/event-server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ function parseDataStoreTxEventData(
287287
block_height: blockData.block_height,
288288
clarity_version: clarityVersion,
289289
source_code: tx.parsed_tx.payload.code_body,
290-
abi: JSON.stringify(tx.core_tx.contract_abi),
290+
abi: JSON.stringify(tx.core_tx.contract_interface ?? tx.core_tx.contract_abi),
291291
canonical: true,
292292
});
293293
break;
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env node
2+
3+
import { connectPostgres, PgSqlClient } from '@hirosystems/api-toolkit';
4+
import { StacksCoreRpcClient } from '../core-rpc/client';
5+
import { getConnectionArgs, getConnectionConfig } from '../datastore/connection';
6+
import { ClarityAbi } from '../event-stream/contract-abi';
7+
import { loadDotEnv } from '../helpers';
8+
import { logger } from '../logger';
9+
10+
async function main() {
11+
// 1) Environment + DB Setup
12+
loadDotEnv();
13+
const sql: PgSqlClient = await connectPostgres({
14+
usageName: 'patch-missing-contract-abis',
15+
connectionArgs: getConnectionArgs(),
16+
connectionConfig: getConnectionConfig(),
17+
});
18+
19+
const BATCH_SIZE = 64;
20+
const LAST_BLOCK_HEIGHT = parseInt(process.env.LAST_BLOCK_HEIGHT ?? '-1');
21+
22+
try {
23+
logger.info('Starting script to patch missing contract ABIs...');
24+
25+
// 2) Initialize script variables and RPC client
26+
let lastBlockHeight = LAST_BLOCK_HEIGHT; // Initial value for the first query
27+
28+
let totalConsideredCount = 0;
29+
let totalPatchedCount = 0;
30+
31+
const rpc = new StacksCoreRpcClient(); // Default to RPC host from ENV
32+
33+
// 3) Main processing loop: Fetch and patch contracts in batches
34+
while (true) {
35+
// 3.1) Find contracts whose ABI is still missing (paginated)
36+
const missing = await sql<{ contract_id: string; block_height: number }[]>`
37+
SELECT sc.contract_id, sc.block_height
38+
FROM smart_contracts sc
39+
JOIN txs ON sc.tx_id = txs.tx_id
40+
WHERE (sc.abi::text = '"null"')
41+
AND sc.canonical = TRUE
42+
AND txs.canonical = TRUE
43+
AND txs.microblock_canonical = TRUE
44+
AND txs.status = 1
45+
AND sc.block_height > ${lastBlockHeight}
46+
ORDER BY sc.block_height ASC
47+
LIMIT ${BATCH_SIZE}
48+
`;
49+
50+
if (missing.length === 0) {
51+
if (totalConsideredCount === 0) {
52+
logger.info(' - No contracts with missing ABI found.');
53+
} else {
54+
logger.info(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`);
55+
}
56+
break; // Exit the while loop
57+
}
58+
59+
logger.info(`- Found batch of ${missing.length} contracts with missing ABIs.`);
60+
61+
// 3.2) Process each contract in the current batch
62+
for (const contract of missing) {
63+
totalConsideredCount++;
64+
const { contract_id, block_height } = contract;
65+
const [address, name] = contract_id.split('.');
66+
if (!address || !name) {
67+
logger.warn(` - Skipping invalid contract id: ${contract_id}`);
68+
continue;
69+
}
70+
71+
try {
72+
// 3.3) Fetch ABI from the connected Stacks node
73+
const abi = await rpc.fetchJson<ClarityAbi>(`v2/contracts/interface/${address}/${name}`);
74+
75+
if (!abi || typeof abi !== 'object' || Object.keys(abi).length === 0) {
76+
logger.warn(` - Skipping ${contract_id}. Fetched empty or invalid ABI.`);
77+
continue;
78+
}
79+
80+
if (typeof abi === 'string' && abi === 'null') {
81+
logger.warn(` - Skipping ${contract_id}. Fetched "null" string ABI.`);
82+
continue;
83+
}
84+
85+
// 3.4) Update row for this contract still missing an ABI
86+
const rows = await sql`
87+
UPDATE smart_contracts
88+
SET abi = ${abi}
89+
WHERE contract_id = ${contract_id}
90+
AND (abi::text = '"null"')
91+
AND canonical = TRUE
92+
`;
93+
if (rows.count === 0) {
94+
logger.warn(` - Failed to patch ${contract_id}. No rows updated.`);
95+
continue;
96+
}
97+
98+
logger.info(` - Patched ABI for ${contract_id}`);
99+
totalPatchedCount++;
100+
} catch (err: any) {
101+
logger.error(err, ` - Failed to patch ${contract_id}`);
102+
}
103+
104+
// Keep track of the latest block_height we've processed
105+
if (block_height > lastBlockHeight) {
106+
lastBlockHeight = block_height;
107+
logger.info(` - Processed up to block ${lastBlockHeight}`);
108+
}
109+
}
110+
111+
// 3.5) Check if it was the last batch
112+
if (missing.length < BATCH_SIZE) {
113+
logger.info(` - Patched ${totalPatchedCount}/${totalConsideredCount} contracts.`);
114+
break; // Last batch was smaller than batch size, so no more items.
115+
}
116+
}
117+
} catch (err: any) {
118+
logger.error(err, 'An unexpected error occurred');
119+
throw err;
120+
} finally {
121+
// 4) Close DB connection
122+
logger.info('Closing database connection...');
123+
await sql.end({ timeout: 5 });
124+
logger.info('Done.');
125+
}
126+
}
127+
128+
main().catch(err => {
129+
logger.error(err, 'An unexpected error occurred');
130+
process.exit(1);
131+
});

tests/api/address.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2529,7 +2529,7 @@ describe('address tests', () => {
25292529
raw_result: '0x0100000000000000000000000000000001', // u1
25302530
txid: '0x' + txBuilder.txid(),
25312531
tx_index: 2,
2532-
contract_abi: null,
2532+
contract_interface: null,
25332533
microblock_hash: null,
25342534
microblock_parent_hash: null,
25352535
microblock_sequence: null,

tests/api/tx.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -387,7 +387,7 @@ describe('tx tests', () => {
387387
raw_result: '0x0100000000000000000000000000000001', // u1
388388
txid: tx.tx_id,
389389
tx_index: 2,
390-
contract_abi: abiSample,
390+
contract_interface: abiSample,
391391
microblock_hash: null,
392392
microblock_parent_hash: null,
393393
microblock_sequence: null,
@@ -548,7 +548,7 @@ describe('tx tests', () => {
548548
raw_result: '0x0100000000000000000000000000000001', // u1
549549
txid: tx.tx_id,
550550
tx_index: 2,
551-
contract_abi: null,
551+
contract_interface: null,
552552
microblock_hash: null,
553553
microblock_parent_hash: null,
554554
microblock_sequence: null,
@@ -699,7 +699,7 @@ describe('tx tests', () => {
699699
raw_result: '0x0100000000000000000000000000000001', // u1
700700
txid: tx.tx_id,
701701
tx_index: 2,
702-
contract_abi: null,
702+
contract_interface: null,
703703
microblock_hash: null,
704704
microblock_parent_hash: null,
705705
microblock_sequence: null,
@@ -861,7 +861,7 @@ describe('tx tests', () => {
861861
raw_result: '0x0100000000000000000000000000000001', // u1
862862
txid: '0x' + txBuilder.txid(),
863863
tx_index: 2,
864-
contract_abi: null,
864+
contract_interface: null,
865865
microblock_hash: null,
866866
microblock_parent_hash: null,
867867
microblock_sequence: null,
@@ -1087,7 +1087,7 @@ describe('tx tests', () => {
10871087
raw_result: '0x0100000000000000000000000000000001', // u1
10881088
txid: '0x' + txBuilder.txid(),
10891089
tx_index: 2,
1090-
contract_abi: null,
1090+
contract_interface: null,
10911091
microblock_hash: null,
10921092
microblock_parent_hash: null,
10931093
microblock_sequence: null,
@@ -1493,7 +1493,7 @@ describe('tx tests', () => {
14931493
raw_result: '0x0100000000000000000000000000000001', // u1
14941494
txid: '0x' + txBuilder.txid(),
14951495
tx_index: 2,
1496-
contract_abi: null,
1496+
contract_interface: null,
14971497
microblock_hash: null,
14981498
microblock_parent_hash: null,
14991499
microblock_sequence: null,
@@ -1728,7 +1728,7 @@ describe('tx tests', () => {
17281728
status: 'abort_by_response',
17291729
txid: '0x' + txBuilder.txid(),
17301730
tx_index: 2,
1731-
contract_abi: null,
1731+
contract_interface: null,
17321732
microblock_hash: null,
17331733
microblock_parent_hash: null,
17341734
microblock_sequence: null,
@@ -1886,7 +1886,7 @@ describe('tx tests', () => {
18861886
status: 'abort_by_post_condition',
18871887
txid: '0x' + txBuilder.txid(),
18881888
tx_index: 2,
1889-
contract_abi: null,
1889+
contract_interface: null,
18901890
microblock_hash: null,
18911891
microblock_parent_hash: null,
18921892
microblock_sequence: null,

0 commit comments

Comments
 (0)