Skip to content

Commit 12b9e48

Browse files
authored
Replace script config for fixing mismatched metadata (#2285)
* Add replaceMetadata method to customReplaceMethods * Add excludeContract option to ReplaceConfig and implement contract skipping logic * Add error handling for on-chain creation bytecode fetching in replaceContract * Add configuration for replacing metadata in sourcify_matches table via massive replace script
1 parent 35218bc commit 12b9e48

File tree

6 files changed

+149
-3
lines changed

6 files changed

+149
-3
lines changed

services/database/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
migrations-temp/
2-
massive-replace-script/CURRENT_VERIFIED_CONTRACT
2+
massive-replace-script/CURRENT_VERIFIED_CONTRACT
3+
massive-replace-script/FAILED_CONTRACTS
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Configuration for replacing metadata in sourcify_matches table
2+
// This configuration targets contracts with partial runtime matches where metadata needs to be updated
3+
// because it does not match the sources in the database.
4+
// Only processes contracts where source hashes don't match the existing metadata
5+
6+
const { id: keccak256str } = require("ethers");
7+
8+
module.exports = {
9+
query: async (sourcePool, sourcifySchema, currentVerifiedContract, n) => {
10+
return await sourcePool.query(
11+
`
12+
SELECT
13+
cd.chain_id,
14+
cd.address,
15+
sm.id as verified_contract_id,
16+
json_build_object(
17+
'language', INITCAP(cc.language),
18+
'sources', json_object_agg(compiled_contracts_sources.path, json_build_object('content', sources.content)),
19+
'settings', cc.compiler_settings
20+
) as std_json_input,
21+
cc.version as compiler_version,
22+
cc.fully_qualified_name,
23+
sm.metadata
24+
FROM ${sourcifySchema}.sourcify_matches sm
25+
JOIN ${sourcifySchema}.verified_contracts vc ON sm.verified_contract_id = vc.id
26+
JOIN ${sourcifySchema}.contract_deployments cd ON vc.deployment_id = cd.id
27+
JOIN ${sourcifySchema}.compiled_contracts cc ON vc.compilation_id = cc.id
28+
JOIN ${sourcifySchema}.compiled_contracts_sources ON compiled_contracts_sources.compilation_id = cc.id
29+
LEFT JOIN ${sourcifySchema}.sources ON sources.source_hash = compiled_contracts_sources.source_hash
30+
WHERE sm.created_at < '2024-08-29 08:58:57 +0200'
31+
AND sm.runtime_match ='partial'
32+
AND sm.id >= $1
33+
GROUP BY sm.id, vc.id, cc.id, cd.id
34+
ORDER BY sm.id ASC
35+
LIMIT $2
36+
`,
37+
[currentVerifiedContract, n],
38+
);
39+
},
40+
buildRequestBody: (contract) => {
41+
return {
42+
chainId: contract.chain_id.toString(),
43+
address: `0x${contract.address.toString("hex")}`,
44+
forceCompilation: true,
45+
jsonInput: contract.std_json_input,
46+
compilerVersion: contract.compiler_version,
47+
compilationTarget: contract.fully_qualified_name,
48+
forceRPCRequest: false,
49+
customReplaceMethod: "replace-metadata",
50+
};
51+
},
52+
excludeContract: (contract) => {
53+
const address = `0x${contract.address.toString("hex")}`;
54+
const sources = contract.std_json_input.sources;
55+
const currentMetadata = contract.metadata;
56+
57+
if (!sources || !currentMetadata) {
58+
console.log(
59+
`Contract address=${address}, chain_id=${contract.chain_id}: Missing sources or metadata -> skipping`,
60+
);
61+
return true; // Exclude if no sources or metadata
62+
}
63+
64+
if (
65+
Object.keys(sources).length !==
66+
Object.keys(currentMetadata.sources).length
67+
) {
68+
console.log(
69+
`Contract address=${address}, chain_id=${contract.chain_id}: wrong sources length: ${Object.keys(sources).length} (std json) vs ${Object.keys(currentMetadata.sources).length} (metadata)`,
70+
);
71+
return false; // something is wrong -> replace metadata
72+
}
73+
74+
for (const [sourcePath, sourceMetadata] of Object.entries(
75+
currentMetadata.sources,
76+
)) {
77+
const expectedHash = sourceMetadata.keccak256;
78+
79+
if (!sources[sourcePath]) {
80+
console.log(
81+
`Contract address=${address}, chain_id=${contract.chain_id}: Metadata source ${sourcePath} not in sources`,
82+
);
83+
return false; // something is wrong -> replace metadata
84+
}
85+
86+
const contentHash = keccak256str(sources[sourcePath].content);
87+
88+
if (contentHash !== expectedHash) {
89+
console.log(
90+
`Contract address=${address}, chain_id=${contract.chain_id}: ContentHash does not match metadata hash for source ${sourcePath}: ${contentHash} (std json) vs ${expectedHash} (metadata)`,
91+
);
92+
return false; // something is wrong -> replace metadata
93+
}
94+
}
95+
96+
return true; // All sources match the metadata, exclude this contract
97+
},
98+
description:
99+
"Replaces metadata in sourcify_matches table for contracts where source content hashes don't match the existing metadata hashes.",
100+
};

services/database/massive-replace-script/massive-replace-script.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ interface ReplaceConfig {
1313
n: number,
1414
) => Promise<pg.QueryResult>;
1515
buildRequestBody: (contract: any) => any;
16+
excludeContract?: (contract: any) => boolean;
1617
description?: string;
1718
}
1819

@@ -136,6 +137,13 @@ async function processContract(
136137
config: ReplaceConfig,
137138
): Promise<void> {
138139
const address = `0x${contract.address.toString("hex")}`;
140+
if (config.excludeContract && config.excludeContract(contract)) {
141+
console.log(
142+
`Skipping contract: chainId=${contract.chain_id}, address=${address}, verifiedContractId=${contract.verified_contract_id}`,
143+
);
144+
return;
145+
}
146+
139147
try {
140148
console.log(
141149
`Processing contract: chainId=${contract.chain_id}, address=${address}, verifiedContractId=${contract.verified_contract_id}`,

services/server/src/server/apiv1/verification/private/stateless/customReplaceMethods.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,34 @@ export const replaceCreationInformation: CustomReplaceMethod = async (
127127
});
128128
};
129129

130+
export const replaceMetadata: CustomReplaceMethod = async (
131+
sourcifyDatabaseService: SourcifyDatabaseService,
132+
verification: VerificationExport,
133+
) => {
134+
const existingSourcifyMatch =
135+
await sourcifyDatabaseService.database.getSourcifyMatchByChainAddressWithProperties(
136+
verification.chainId,
137+
bytesFromString(verification.address),
138+
["id"],
139+
);
140+
if (existingSourcifyMatch.rows.length === 0) {
141+
throw new Error(
142+
`No existing verified contract found for address ${verification.address} on chain ${verification.chainId}`,
143+
);
144+
}
145+
146+
const matchId = existingSourcifyMatch.rows[0].id;
147+
148+
await sourcifyDatabaseService.database.pool.query(
149+
`UPDATE sourcify_matches
150+
SET
151+
metadata = $2
152+
WHERE id = $1`,
153+
[matchId, verification.compilation.metadata],
154+
);
155+
};
156+
130157
export const REPLACE_METHODS: Record<string, CustomReplaceMethod> = {
131158
"replace-creation-information": replaceCreationInformation,
159+
"replace-metadata": replaceMetadata,
132160
};

services/server/src/server/apiv1/verification/private/stateless/private.stateless.handlers.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,14 +315,22 @@ export async function replaceContract(
315315
throw error;
316316
}
317317

318+
let rpcFailedFetchingCreationBytecode = false;
319+
try {
320+
rpcFailedFetchingCreationBytecode =
321+
verification.onchainCreationBytecode === undefined;
322+
} catch (error) {
323+
// verification.onchainCreationBytecode throws if not available
324+
rpcFailedFetchingCreationBytecode = true;
325+
}
326+
318327
res.send({
319328
replaced: true,
320329
address: address,
321330
chainId: chainId,
322331
transactionHash: transactionHash,
323332
newStatus: verificationStatus,
324-
rpcFailedFetchingCreationBytecode:
325-
verification.onchainCreationBytecode === undefined,
333+
rpcFailedFetchingCreationBytecode,
326334
});
327335
} catch (error: any) {
328336
throw new InternalServerError(error.message);

services/server/src/server/apiv1/verification/private/stateless/private.stateless.paths.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ paths:
201201
creation match status while preserving the runtime verification data. This method is
202202
useful for correcting incomplete or missing creation data on contracts that were
203203
previously verified only against runtime bytecode.
204+
- "replace-metadata": Updates only the metadata of an existing verified contract.
204205
example: "replace-creation-information"
205206
responses:
206207
"200":

0 commit comments

Comments
 (0)