Skip to content

Commit 4dceb52

Browse files
authored
Replace script config for fixing missing constructor args transformations (#2282)
* Add configuration for replacing contracts with missing constructorArguments transformation * Fix README path for configuration file in massive-replace script usage instructions * Fix transaction hash assignment in replaceContract and fix error message in replaceCreationInformation * Await all promises to settle in massive-replace-script before running next batch * Add support for storing failed contracts in massive replace script
1 parent 18371d6 commit 4dceb52

File tree

6 files changed

+91
-15
lines changed

6 files changed

+91
-15
lines changed

services/database/.env.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ DBMATE_SCHEMA_FILE=./sourcify-database.sql
2626
# Database configuration uses the POSTGRES_ variables above
2727
# API configuration
2828
API_BASE_URL=https://staging.sourcify.dev/server
29-
API_AUTH_TOKEN=token_here
29+
API_AUTH_TOKEN=token_here
30+
STORE_FAILED_CONTRACT_IDS=false

services/database/massive-replace-script/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ module.exports = {
6666

6767
```bash
6868
cd services/database
69-
CONFIG_FILE_PATH=./massive-replace-script/config-replace-creation-information.js npm run massive-replace
69+
CONFIG_FILE_PATH=./config-replace-creation-information.js npm run massive-replace
7070
```
7171

7272
## How It Works
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Configuration for fixing missing constructorArguments transformation
2+
// This configuration targets contracts where creation code exists but creation_match is null/false
3+
// and the recompiled creation code matches the onchain creation code
4+
// Related to: https://github.com/ethereum/sourcify/issues/2086
5+
6+
module.exports = {
7+
query: async (sourcePool, sourcifySchema, currentVerifiedContract, n) => {
8+
return await sourcePool.query(
9+
`
10+
SELECT
11+
cd.chain_id,
12+
cd.address,
13+
sm.id as verified_contract_id
14+
FROM ${sourcifySchema}.verified_contracts vc
15+
JOIN ${sourcifySchema}.contract_deployments cd ON vc.deployment_id = cd.id
16+
JOIN ${sourcifySchema}.contracts c ON cd.contract_id = c.id
17+
JOIN ${sourcifySchema}.code creation_code ON c.creation_code_hash = creation_code.code_hash
18+
JOIN ${sourcifySchema}.compiled_contracts cc ON vc.compilation_id = cc.id
19+
JOIN ${sourcifySchema}.code recompiled_creation_code ON cc.creation_code_hash = recompiled_creation_code.code_hash
20+
INNER JOIN ${sourcifySchema}.sourcify_matches sm ON sm.verified_contract_id = vc.id
21+
WHERE c.creation_code_hash IS NOT NULL
22+
AND position(substring(recompiled_creation_code.code for 200) in creation_code.code) != 0
23+
AND (vc.creation_match is null or vc.creation_match = false)
24+
AND sm.id >= $1
25+
ORDER BY sm.id ASC
26+
LIMIT $2
27+
`,
28+
[currentVerifiedContract, n],
29+
);
30+
},
31+
buildRequestBody: (contract) => {
32+
return {
33+
chainId: contract.chain_id.toString(),
34+
address: `0x${contract.address.toString("hex")}`,
35+
forceCompilation: false,
36+
forceRPCRequest: false,
37+
customReplaceMethod: "replace-creation-information",
38+
};
39+
},
40+
description:
41+
"Fixes missing constructorArguments transformation for contracts where creation code exists but creation_match is null/false. See https://github.com/ethereum/sourcify/issues/2086",
42+
};

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

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,35 @@ if (fs.existsSync(COUNTER_FILE)) {
3434
);
3535
}
3636

37+
// Optional failed contracts storage
38+
const STORE_FAILED_CONTRACT_IDS =
39+
process.env.STORE_FAILED_CONTRACT_IDS === "true";
40+
const FAILED_CONTRACTS_FILE = path.join(
41+
CURRENT_VERIFIED_CONTRACT_PATH,
42+
"FAILED_CONTRACTS",
43+
);
44+
45+
function storeFailedContract(contract: any, error: any): void {
46+
if (!STORE_FAILED_CONTRACT_IDS) return;
47+
48+
const address = `0x${contract.address.toString("hex")}`;
49+
const failedContractInfo = {
50+
timestamp: new Date().toISOString(),
51+
verifiedContractId: contract.verified_contract_id,
52+
chainId: contract.chain_id,
53+
address: address,
54+
error: error.message || error.toString(),
55+
};
56+
57+
const logEntry = JSON.stringify(failedContractInfo) + "\n";
58+
59+
try {
60+
fs.appendFileSync(FAILED_CONTRACTS_FILE, logEntry, "utf8");
61+
} catch (writeError) {
62+
console.error("Error writing to failed contracts file:", writeError);
63+
}
64+
}
65+
3766
const N = 5; // Number of contracts to process at a time
3867

3968
const POSTGRES_SCHEMA = process.env.POSTGRES_SCHEMA || "public";
@@ -109,7 +138,7 @@ async function processContract(
109138
const address = `0x${contract.address.toString("hex")}`;
110139
try {
111140
console.log(
112-
`Processing contract: chainId=${contract.chain_id}, address=0x${address}, verifiedContractId=${contract.verified_contract_id || contract.id}`,
141+
`Processing contract: chainId=${contract.chain_id}, address=${address}, verifiedContractId=${contract.verified_contract_id}`,
113142
);
114143

115144
const requestBody = config.buildRequestBody(contract);
@@ -121,6 +150,7 @@ async function processContract(
121150
`❌ Failed to process contract ${address} at chain ${contract.chain_id}:`,
122151
error,
123152
);
153+
storeFailedContract(contract, error);
124154
throw error;
125155
}
126156
}
@@ -132,6 +162,10 @@ async function processContract(
132162
`Using configuration: ${config.description || "Custom replacement"}`,
133163
);
134164

165+
if (STORE_FAILED_CONTRACT_IDS) {
166+
console.log(`Failed contracts will be stored in: ${FAILED_CONTRACTS_FILE}`);
167+
}
168+
135169
// Connect to source DB using a Pool
136170
const sourcePool = new Pool(SOURCE_DB_CONFIG);
137171
sourcePool.on("error", (err) => {
@@ -160,21 +194,20 @@ async function processContract(
160194

161195
let secondToWait = 2;
162196
// Process the batch in parallel
163-
try {
164-
const processingPromises = verifiedContracts.map((contract) =>
165-
processContract(contract, config),
166-
);
167-
await Promise.all(processingPromises);
168-
} catch (batchError) {
169-
secondToWait = 5; // Increase wait time on error
170-
console.error("Error processing batch:", batchError);
197+
const processingPromises = verifiedContracts.map((contract) =>
198+
processContract(contract, config),
199+
);
200+
const results = await Promise.allSettled(processingPromises);
201+
for (const result of results) {
202+
if (result.status === "rejected") {
203+
secondToWait = 5; // Increase wait time on error
204+
}
171205
}
172206

173207
// Update the counter file only after the batch successfully completes
174208

175209
const lastProcessedId =
176-
verifiedContracts[verifiedContracts.length - 1].verified_contract_id ||
177-
verifiedContracts[verifiedContracts.length - 1].id;
210+
verifiedContracts[verifiedContracts.length - 1].verified_contract_id;
178211
CURRENT_VERIFIED_CONTRACT = parseInt(lastProcessedId) + 1;
179212

180213
// Use async write to avoid blocking

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const replaceCreationInformation: CustomReplaceMethod = async (
2121
await sourcifyDatabaseService.withTransaction(async (poolClient) => {
2222
if (!databaseColumns.onchainCreationCode) {
2323
throw new Error(
24-
"Creation match is null, cannot replace creation information",
24+
"No onchain creation code, cannot replace creation information",
2525
);
2626
}
2727
// Get existing verified contract to find deployment_id

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ export async function replaceContract(
266266
const transactionHashFromDatabase = (sourcifyChain as SourcifyChainMock)
267267
.contractDeployment?.transaction_hash;
268268
if (transactionHashFromDatabase) {
269-
transactionHash = `0x${transactionHashFromDatabase}`;
269+
transactionHash = transactionHashFromDatabase;
270270
}
271271
} else {
272272
// Use the chainRepository to get the sourcifyChain object and fetch the contract's information from the RPC

0 commit comments

Comments
 (0)