Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bytecode hash calculation #1414

Merged
merged 18 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
17f14c1
Fix bytecode hash calculation by updating variable names
marcocastignoli May 30, 2024
4bc675b
update temporary new tables FKs
marcocastignoli May 30, 2024
65c97dc
update tmp-tables db scripts
marcocastignoli May 31, 2024
c4976d7
prevent calling getTxReceipt twice during matchWithCreationTx
marcocastignoli Jun 4, 2024
c10568f
remove database migration to add code_new, contracts_new, compiled_co…
marcocastignoli Jun 4, 2024
8256b5b
implement sourcifyFixedDatabase as storageService
marcocastignoli Jun 4, 2024
b12ab64
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 4, 2024
2c0de2a
Update database migration script
marcocastignoli Jun 5, 2024
8028121
Update packages/lib-sourcify/src/lib/SourcifyChain.ts
marcocastignoli Jun 5, 2024
7e00318
remove old comment "For now disable the creation tests"
marcocastignoli Jun 5, 2024
75ad15e
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 6, 2024
c4cfdc0
add tests and fix compiler setting generation
marcocastignoli Jun 6, 2024
b451810
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 6, 2024
c2ee725
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 10, 2024
25b9c9f
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 12, 2024
a0e3be3
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 13, 2024
4fac145
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 13, 2024
10111b9
Merge branch 'staging' into fix-inverted-code-hashes
marcocastignoli Jun 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions packages/lib-sourcify/src/lib/SourcifyChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,18 @@
);
};

getContractCreationBytecode = async (
getContractCreationBytecodeAndReceipt = async (
address: string,
transactionHash: string,
creatorTx?: TransactionResponse
): Promise<string> => {
): Promise<{
creationBytecode: string;
txReceipt: TransactionReceipt;
}> => {
const txReceipt = await this.getTxReceipt(transactionHash);
if (!creatorTx) creatorTx = await this.getTx(transactionHash);
let creationBytecode = '';

let creationBytecode;
// Non null txreceipt.contractAddress means that the contract was created with an EOA
if (txReceipt.contractAddress !== null) {
if (txReceipt.contractAddress !== address) {
Expand Down Expand Up @@ -416,6 +419,13 @@
}
}

return creationBytecode;
if (!creationBytecode) {
throw new Error('Cannot get creation bytecode');
}

Check warning on line 424 in packages/lib-sourcify/src/lib/SourcifyChain.ts

View check run for this annotation

Codecov / codecov/patch

packages/lib-sourcify/src/lib/SourcifyChain.ts#L423-L424

Added lines #L423 - L424 were not covered by tests

return {
creationBytecode,
txReceipt,
};
};
}
37 changes: 18 additions & 19 deletions packages/lib-sourcify/src/lib/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,36 +533,35 @@ export async function matchWithCreationTx(
return;
}

const creatorTx = await sourcifyChain.getTx(creatorTxHash);
let onchainCreationBytecode = '';
// Call rpc to find creatorTx, txReceipt and onchainContractCreationBytecode
// return null creationMatch if fail
let creatorTx;
try {
onchainCreationBytecode =
(await sourcifyChain.getContractCreationBytecode(
creatorTx = await sourcifyChain.getTx(creatorTxHash);
match.creatorTxHash = creatorTxHash;
match.blockNumber = creatorTx.blockNumber;
match.deployer = creatorTx.from;

const { creationBytecode, txReceipt } =
await sourcifyChain.getContractCreationBytecodeAndReceipt(
marcocastignoli marked this conversation as resolved.
Show resolved Hide resolved
address,
creatorTxHash,
creatorTx
)) || '';
);
match.onchainCreationBytecode = creationBytecode;
match.txIndex = txReceipt.index;
} catch (e: any) {
logWarn('Failed to fetch creation bytecode', {
address,
txHash: creatorTxHash,
chainId: sourcifyChain.chainId.toString(),
error: e,
});
match.creationMatch = null;
match.message = `Failed to match with creation bytecode: couldn't get the creation bytecode.`;
return;
}

// txIndex is available only in the receipt
const txReceipt = await sourcifyChain.getTxReceipt(creatorTxHash);
match.txIndex = txReceipt.index;

match.creatorTxHash = creatorTxHash;
match.blockNumber = creatorTx.blockNumber;
match.deployer = creatorTx.from;

match.onchainCreationBytecode = onchainCreationBytecode;

// Initialize the transformations array if undefined
if (match.creationTransformations === undefined) {
match.creationTransformations = [];
Expand All @@ -575,7 +574,7 @@ export async function matchWithCreationTx(
// Replace the library placeholders in the recompiled bytecode with values from the deployed bytecode
const { replaced, libraryMap } = addLibraryAddresses(
recompiledCreationBytecode,
onchainCreationBytecode,
match.onchainCreationBytecode,
match.creationTransformations
);
recompiledCreationBytecode = replaced;
Expand All @@ -589,7 +588,7 @@ export async function matchWithCreationTx(
}, {});
}

if (onchainCreationBytecode.startsWith(recompiledCreationBytecode)) {
if (match.onchainCreationBytecode.startsWith(recompiledCreationBytecode)) {
// if the bytecode doesn't end with metadata then "partial" match
if (endsWithMetadataHash(recompiledCreationBytecode)) {
match.creationMatch = 'perfect';
Expand Down Expand Up @@ -626,7 +625,7 @@ export async function matchWithCreationTx(
creationTransformationsValuesCborAuxdata,
} = normalizeBytecodesAuxdata(
recompiledCreationBytecode,
onchainCreationBytecode,
match.onchainCreationBytecode,
cborAuxdataPositions
)!;

Expand All @@ -648,7 +647,7 @@ export async function matchWithCreationTx(
if (match.creationMatch) {
const abiEncodedConstructorArguments =
extractAbiEncodedConstructorArguments(
onchainCreationBytecode,
match.onchainCreationBytecode,
recompiledCreationBytecode
);
const constructorAbiParamInputs = (
Expand Down
56 changes: 48 additions & 8 deletions services/database/scripts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ program
});

for (const contract of contractsSorted) {
await databasePool.query(
await executeQueryWithRetry(
databasePool,
`INSERT INTO sourcify_sync (chain_id, address, match_type, created_at) VALUES ($1, $2, $3, $4) ON CONFLICT (chain_id, address) DO NOTHING`,
[
contract.chainId,
Expand Down Expand Up @@ -151,7 +152,11 @@ program
"Path to repository v1 contracts folder (e.g. /Users/user/sourcify/repository/contracts)"
)
.option(
"-c, --chainsExceptions [items]",
"-c, --chains [items]",
"List of chains to sync separated by comma (e.g. 1,5,...)"
)
.option(
"-ce, --chainsExceptions [items]",
"List of chains exceptions separated by comma (e.g. 1,5,...)"
)
.option("-sf, --start-from [number]", "Start from a specific timestamp (ms)")
Expand All @@ -176,15 +181,24 @@ program
// Get chains from database
let chains = [];
const query = "SELECT DISTINCT chain_id FROM sourcify_sync";
const chainsResult = await databasePool.query(query);
const chainsResult = await executeQueryWithRetry(databasePool, query);
if (chainsResult.rowCount > 0) {
chains = chainsResult.rows.map(({ chain_id }) => chain_id);
}

// Specify which chain to sync
if (options.chains) {
chains = chains.filter((chain) =>
options.chains.split(",").includes(`${chain}`)
);
}

// Remove exceptions using --chainsException and 0
chains = chains.filter(
(chain) => !options.chainsExceptions?.split(",").includes(`${chain}`)
);
if (options.chainsExceptions) {
chains = chains.filter(
(chain) => !options.chainsExceptions.split(",").includes(`${chain}`)
);
}

let monitoring = {
totalSynced: 0,
Expand Down Expand Up @@ -315,7 +329,8 @@ const processContract = async (
if (request.status === 200) {
const response = await request.json();
if (response.result[0].status !== null) {
await databasePool.query(
await executeQueryWithRetry(
databasePool,
`
UPDATE sourcify_sync
SET
Expand Down Expand Up @@ -368,7 +383,7 @@ const fetchNextContract = async (databasePool, options, chainId) => {
ORDER BY id ASC
LIMIT 1
`;
const contractResult = await databasePool.query(query, [
const contractResult = await executeQueryWithRetry(databasePool, query, [
options.startFrom ? options.startFrom : 0,
chainId,
]);
Expand All @@ -388,4 +403,29 @@ function isNumber(value) {
return !isNaN(parseFloat(value)) && isFinite(value);
}

const executeQueryWithRetry = async (
databasePool,
query,
params,
maxRetries = 5,
delay = 5000
) => {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
const result = await databasePool.query(query, params);
return result;
} catch (error) {
if (error.code === "ECONNREFUSED" || error.code === "ETIMEDOUT") {
console.error(
`Database connection error. Retrying attempt ${attempt + 1}...`
);
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw error;
}
}
}
throw new Error("Max retries reached. Could not connect to the database.");
};

program.parse(process.argv);
30 changes: 30 additions & 0 deletions services/server/src/server/services/StorageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,15 @@
repositoryV1ServiceOptions: RepositoryV1ServiceOptions;
repositoryV2ServiceOptions: RepositoryV2ServiceOptions;
sourcifyDatabaseServiceOptions?: SourcifyDatabaseServiceOptions;
sourcifyFixedDatabaseServiceOptions?: SourcifyDatabaseServiceOptions;
allianceDatabaseServiceOptions?: AllianceDatabaseServiceOptions;
}

export class StorageService {
repositoryV1: RepositoryV1Service;
repositoryV2?: RepositoryV2Service;
sourcifyDatabase?: SourcifyDatabaseService;
sourcifyFixedDatabase?: SourcifyDatabaseService;
allianceDatabase?: AllianceDatabaseService;

constructor(options: StorageServiceOptions) {
Expand Down Expand Up @@ -86,6 +88,23 @@
);
}

// SourcifyFixedDatabase
if (
options.sourcifyFixedDatabaseServiceOptions?.postgres?.host &&
options.sourcifyFixedDatabaseServiceOptions?.postgres?.database &&
options.sourcifyFixedDatabaseServiceOptions?.postgres?.user &&
options.sourcifyFixedDatabaseServiceOptions?.postgres?.password

Check warning on line 96 in services/server/src/server/services/StorageService.ts

View check run for this annotation

Codecov / codecov/patch

services/server/src/server/services/StorageService.ts#L96

Added line #L96 was not covered by tests
) {
this.sourcifyFixedDatabase = new SourcifyDatabaseService(
manuelwedler marked this conversation as resolved.
Show resolved Hide resolved
options.sourcifyFixedDatabaseServiceOptions
);

Check warning on line 100 in services/server/src/server/services/StorageService.ts

View check run for this annotation

Codecov / codecov/patch

services/server/src/server/services/StorageService.ts#L98-L100

Added lines #L98 - L100 were not covered by tests
} else {
logger.warn(
"Won't use SourcifyFixedDatabase, options not complete",
options.sourcifyFixedDatabaseServiceOptions
);
}

// AllianceDatabase
if (
options.allianceDatabaseServiceOptions?.googleCloudSql ||
Expand Down Expand Up @@ -431,6 +450,17 @@
})
);

if (this.sourcifyFixedDatabase) {
promises.push(
this.sourcifyFixedDatabase.storeMatch(contract, match).catch((e) => {
logger.error("Error storing to SourcifyFixedDatabase: ", {
error: e,
});
throw e;
})
);
}

Check warning on line 462 in services/server/src/server/services/StorageService.ts

View check run for this annotation

Codecov / codecov/patch

services/server/src/server/services/StorageService.ts#L454-L462

Added lines #L454 - L462 were not covered by tests

promises.push(
this.repositoryV2.storeMatch(contract, match).catch((e) => {
logger.error("Error storing to RepositoryV2: ", {
Expand Down
9 changes: 9 additions & 0 deletions services/server/src/server/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,15 @@ export class Services {
port: parseInt(process.env.SOURCIFY_POSTGRES_PORT || "5432"),
},
},
sourcifyFixedDatabaseServiceOptions: {
postgres: {
host: process.env.SOURCIFY_FIXED_POSTGRES_HOST as string,
database: process.env.SOURCIFY_FIXED_POSTGRES_DB as string,
user: process.env.SOURCIFY_FIXED_POSTGRES_USER as string,
password: process.env.SOURCIFY_FIXED_POSTGRES_PASSWORD as string,
port: parseInt(process.env.SOURCIFY_FIXED_POSTGRES_PORT || "5432"),
},
},
allianceDatabaseServiceOptions: {
postgres: {
host: process.env.ALLIANCE_POSTGRES_HOST as string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,12 @@ export default abstract class AbstractDatabaseService {

return {
bytecodeHashes: {
recompiledCreation: bytesFromString(keccak256OnchainCreationBytecode),
recompiledRuntime: bytesFromString(keccak256OnchainRuntimeBytecode)!,
onchainCreation: bytesFromString(keccak256RecompiledCreationBytecode),
onchainRuntime: bytesFromString(keccak256RecompiledRuntimeBytecode)!,
recompiledCreation: bytesFromString(
keccak256RecompiledCreationBytecode
),
recompiledRuntime: bytesFromString(keccak256RecompiledRuntimeBytecode)!,
onchainCreation: bytesFromString(keccak256OnchainCreationBytecode),
onchainRuntime: bytesFromString(keccak256OnchainRuntimeBytecode)!,
},
compiledContract: {
language,
Expand Down Expand Up @@ -212,7 +214,8 @@ export default abstract class AbstractDatabaseService {
compilation_artifacts:
databaseColumns.compiledContract.compilation_artifacts!,
sources: recompiledContract.solidity,
compiler_settings: recompiledContract.metadata.settings,
compiler_settings:
Database.prepareCompilerSettings(recompiledContract),
creation_code_hash: databaseColumns.bytecodeHashes.recompiledCreation,
runtime_code_hash: databaseColumns.bytecodeHashes.recompiledRuntime,
creation_code_artifacts:
Expand Down
35 changes: 35 additions & 0 deletions services/server/src/server/services/utils/database-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
CheckedContract,
CompiledContractCborAuxdata,
ImmutableReferences,
Libraries,
Match,
Transformation,
TransformationValues,
Expand Down Expand Up @@ -534,6 +535,40 @@ export function normalizeRecompiledBytecodes(
}
}

export function prepareCompilerSettings(recompiledContract: CheckedContract) {
// The metadata.settings contains recompiledContract that is not a field of compiler input
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { compilationTarget, ...restSettings } =
recompiledContract.metadata.settings;

const metadataLibraries =
recompiledContract.metadata.settings?.libraries || {};
restSettings.libraries = Object.keys(metadataLibraries || {}).reduce(
(libraries, libraryKey) => {
// Before Solidity v0.7.5: { "ERC20": "0x..."}
if (!libraryKey.includes(":")) {
if (!libraries[""]) {
libraries[""] = {};
}
// try using the global method, available for pre 0.7.5 versions
libraries[""][libraryKey] = metadataLibraries[libraryKey];
return libraries;
}

// After Solidity v0.7.5: { "ERC20.sol:ERC20": "0x..."}
const [contractPath, contractName] = libraryKey.split(":");
if (!libraries[contractPath]) {
libraries[contractPath] = {};
}
libraries[contractPath][contractName] = metadataLibraries[libraryKey];
return libraries;
},
{} as Libraries
) as any;

return restSettings;
}

export async function countSourcifyMatchAddresses(pool: Pool, chain: number) {
return await pool.query(
`
Expand Down
11 changes: 10 additions & 1 deletion services/server/test/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,16 @@ export async function deployFromAbiAndBytecodeForCreatorTxHash(
`Deployed contract at ${contractAddress} with tx ${creationTx.hash}`
);

return { contractAddress, txHash: creationTx.hash };
const txReceipt = await signer.provider.getTransactionReceipt(
creationTx.hash
);

return {
contractAddress,
txHash: creationTx.hash,
blockNumber: creationTx.blockNumber,
txIndex: txReceipt?.index,
};
}

export async function deployAndVerifyContract(
Expand Down
Loading