Skip to content

Commit 3fa5023

Browse files
authored
Add logic for writing signatures to db inside SourcifyDatabaseService (#2357)
* Add signature-util for extracting SignatureData from ABIs * Add signature tables interfaces * Add logic for writing signatures to SourcifyDatabaseService * Add tests * address PR feedback * Ignore ABI fragments with custom type
1 parent 237e3c5 commit 3fa5023

File tree

9 files changed

+708
-12
lines changed

9 files changed

+708
-12
lines changed

services/server/src/server/services/VerificationService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,14 +345,14 @@ export class VerificationService {
345345
return verificationId;
346346
}
347347

348-
private async verifyViaWorker(
348+
private verifyViaWorker(
349349
verificationId: VerificationJobId,
350350
functionName: string,
351351
input:
352352
| VerifyFromJsonInput
353353
| VerifyFromMetadataInput
354354
| VerifyFromEtherscanInput,
355-
): Promise<void> {
355+
): void {
356356
const task = this.workerPool
357357
.run(input, { name: functionName })
358358
.then((output: VerifyOutput) => {

services/server/src/server/services/storageServices/AbstractDatabaseService.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { VerificationExport } from "@ethereum-sourcify/lib-sourcify";
22
import * as DatabaseUtil from "../utils/database-util";
3-
import { bytesFromString } from "../utils/database-util";
3+
import { bytesFromString, Tables } from "../utils/database-util";
44
import { Database, DatabaseOptions } from "../utils/Database";
55
import { PoolClient, QueryResult } from "pg";
66

@@ -44,7 +44,7 @@ export default abstract class AbstractDatabaseService {
4444
async insertNewVerifiedContract(
4545
databaseColumns: DatabaseUtil.DatabaseColumns,
4646
client: PoolClient,
47-
): Promise<string> {
47+
): Promise<Tables.VerifiedContract["id"]> {
4848
try {
4949
let recompiledCreationCodeInsertResult:
5050
| QueryResult<Pick<DatabaseUtil.Tables.Code, "bytecode_hash">>
@@ -127,7 +127,7 @@ export default abstract class AbstractDatabaseService {
127127
async updateExistingVerifiedContract(
128128
databaseColumns: DatabaseUtil.DatabaseColumns,
129129
client: PoolClient,
130-
): Promise<string> {
130+
): Promise<Tables.VerifiedContract["id"]> {
131131
// runtime bytecodes must exist
132132
if (databaseColumns.recompiledRuntimeCode.bytecode === undefined) {
133133
throw new Error("Missing normalized runtime bytecode");
@@ -224,8 +224,8 @@ export default abstract class AbstractDatabaseService {
224224
poolClient: PoolClient,
225225
): Promise<{
226226
type: "update" | "insert";
227-
verifiedContractId: string;
228-
oldVerifiedContractId?: string;
227+
verifiedContractId: Tables.VerifiedContract["id"];
228+
oldVerifiedContractId?: Tables.VerifiedContract["id"];
229229
}> {
230230
this.validateVerificationBeforeStoring(verification);
231231

services/server/src/server/services/storageServices/SourcifyDatabaseService.ts

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
Field,
1313
FIELDS_TO_STORED_PROPERTIES,
1414
StoredProperties,
15+
Tables,
1516
} from "../utils/database-util";
1617
import {
1718
ContractData,
@@ -28,6 +29,7 @@ import {
2829
VerificationJob,
2930
Match,
3031
VerificationJobId,
32+
BytesKeccak,
3133
} from "../../types";
3234
import Path from "path";
3335
import {
@@ -37,6 +39,7 @@ import {
3739
toMatchLevel,
3840
} from "../utils/util";
3941
import { getAddress, id as keccak256Str } from "ethers";
42+
import { extractSignaturesFromAbi } from "../utils/signature-util";
4043
import { BadRequestError } from "../../../common/errors";
4144
import { RWStorageIdentifiers } from "./identifiers";
4245
import semver from "semver";
@@ -884,6 +887,57 @@ export class SourcifyDatabaseService
884887
});
885888
}
886889

890+
private async storeSignatures(
891+
poolClient: PoolClient,
892+
verifiedContractId: Tables.VerifiedContract["id"],
893+
verification: VerificationExport,
894+
): Promise<void> {
895+
try {
896+
const compiledContractResult =
897+
await this.database.getCompilationIdForVerifiedContract(
898+
verifiedContractId,
899+
poolClient,
900+
);
901+
if (compiledContractResult.rowCount === 0) {
902+
throw new Error(
903+
`No compilation found for verifiedContractId ${verifiedContractId}`,
904+
);
905+
}
906+
const compilationId = compiledContractResult.rows[0].compilation_id;
907+
908+
const abi = verification.compilation.contractCompilerOutput.abi;
909+
if (!abi) {
910+
throw new Error("No ABI found in compilation output");
911+
}
912+
913+
const signatureData = extractSignaturesFromAbi(abi);
914+
const signatureColumns = signatureData.map((sig) => ({
915+
signature_hash_32: bytesFromString<BytesKeccak>(sig.signatureHash32),
916+
signature: sig.signature,
917+
signature_type: sig.signatureType,
918+
}));
919+
920+
await this.database.insertSignatures(signatureColumns, poolClient);
921+
await this.database.insertCompiledContractSignatures(
922+
compilationId,
923+
signatureColumns,
924+
poolClient,
925+
);
926+
927+
logger.info("Stored signatures to SourcifyDatabase", {
928+
verifiedContractId,
929+
compilationId,
930+
signatureCount: signatureData.length,
931+
});
932+
} catch (error) {
933+
// Don't throw on errors, the job should not fail
934+
logger.error("Error storing signatures", {
935+
verifiedContractId,
936+
error: error,
937+
});
938+
}
939+
}
940+
887941
// Override this method to include the SourcifyMatch
888942
async storeVerificationWithPoolClient(
889943
poolClient: PoolClient,
@@ -892,7 +946,7 @@ export class SourcifyDatabaseService
892946
verificationId: VerificationJobId;
893947
finishTime: Date;
894948
},
895-
): Promise<void> {
949+
): Promise<{ verifiedContractId: Tables.VerifiedContract["id"] }> {
896950
try {
897951
const { type, verifiedContractId, oldVerifiedContractId } =
898952
await super.insertOrUpdateVerification(verification, poolClient);
@@ -962,6 +1016,8 @@ export class SourcifyDatabaseService
9621016
poolClient,
9631017
);
9641018
}
1019+
1020+
return { verifiedContractId: verifiedContractId };
9651021
} catch (error: any) {
9661022
logger.error("Error storing verification", {
9671023
error: error,
@@ -970,19 +1026,30 @@ export class SourcifyDatabaseService
9701026
}
9711027
}
9721028

973-
// Override this method to include the SourcifyMatch
9741029
async storeVerification(
9751030
verification: VerificationExport,
9761031
jobData?: {
9771032
verificationId: VerificationJobId;
9781033
finishTime: Date;
9791034
},
9801035
): Promise<void> {
1036+
const { verifiedContractId } = await this.withTransaction(
1037+
async (transactionPoolClient) => {
1038+
return await this.storeVerificationWithPoolClient(
1039+
transactionPoolClient,
1040+
verification,
1041+
jobData,
1042+
);
1043+
},
1044+
);
1045+
1046+
// Separate transaction because storing the verification should not fail
1047+
// if signatures cannot be stored
9811048
await this.withTransaction(async (transactionPoolClient) => {
982-
await this.storeVerificationWithPoolClient(
1049+
await this.storeSignatures(
9831050
transactionPoolClient,
1051+
verifiedContractId,
9841052
verification,
985-
jobData,
9861053
);
9871054
});
9881055
}

services/server/src/server/services/utils/Database.ts

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Pool, PoolClient, QueryResult } from "pg";
2-
import { Bytes } from "../../types";
2+
import { Bytes, BytesKeccak } from "../../types";
33
import {
44
bytesFromString,
55
GetSourcifyMatchByChainAddressResult,
@@ -285,6 +285,16 @@ ${
285285
);
286286
}
287287

288+
async getCompilationIdForVerifiedContract(
289+
verifiedContractId: Tables.VerifiedContract["id"],
290+
poolClient?: PoolClient,
291+
): Promise<QueryResult<Pick<Tables.VerifiedContract, "compilation_id">>> {
292+
return await (poolClient || this.pool).query(
293+
`SELECT compilation_id FROM verified_contracts WHERE id = $1`,
294+
[verifiedContractId],
295+
);
296+
}
297+
288298
async insertSourcifyMatch(
289299
{
290300
verified_contract_id,
@@ -716,6 +726,68 @@ ${
716726
);
717727
}
718728

729+
async insertSignatures(
730+
signatures: Omit<Tables.Signatures, "signature_hash_4">[],
731+
poolClient?: PoolClient,
732+
): Promise<void> {
733+
if (signatures.length === 0) {
734+
return;
735+
}
736+
737+
const valueIndexes: string[] = [];
738+
const queryValues: (BytesKeccak | string)[] = [];
739+
740+
signatures.forEach((_, index) => {
741+
const baseIndex = index * 2 + 1;
742+
valueIndexes.push(`($${baseIndex}, $${baseIndex + 1})`);
743+
});
744+
745+
signatures.forEach(({ signature_hash_32, signature }) => {
746+
queryValues.push(signature_hash_32, signature);
747+
});
748+
749+
await (poolClient || this.pool).query(
750+
`INSERT INTO ${this.schema}.signatures (signature_hash_32, signature)
751+
VALUES ${valueIndexes.join(", ")}
752+
ON CONFLICT (signature_hash_32) DO NOTHING`,
753+
queryValues,
754+
);
755+
}
756+
757+
async insertCompiledContractSignatures(
758+
compilation_id: string,
759+
signatures: Omit<
760+
Tables.CompiledContractsSignatures,
761+
"id" | "compilation_id"
762+
>[],
763+
poolClient?: PoolClient,
764+
): Promise<void> {
765+
if (signatures.length === 0) {
766+
return;
767+
}
768+
769+
const valueIndexes: string[] = [];
770+
const queryValues: (BytesKeccak | string)[] = [];
771+
772+
signatures.forEach((_, index) => {
773+
const baseIndex = index * 3 + 1;
774+
valueIndexes.push(
775+
`($${baseIndex}, $${baseIndex + 1}, $${baseIndex + 2})`,
776+
);
777+
});
778+
779+
signatures.forEach(({ signature_hash_32, signature_type }) => {
780+
queryValues.push(compilation_id, signature_hash_32, signature_type);
781+
});
782+
783+
await (poolClient || this.pool).query(
784+
`INSERT INTO ${this.schema}.compiled_contracts_signatures (compilation_id, signature_hash_32, signature_type)
785+
VALUES ${valueIndexes.join(", ")}
786+
ON CONFLICT (compilation_id, signature_hash_32, signature_type) DO NOTHING`,
787+
queryValues,
788+
);
789+
}
790+
719791
async insertVerifiedContract(
720792
poolClient: PoolClient,
721793
{

services/server/src/server/services/utils/database-util.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,19 @@ export namespace Tables {
164164
onchain_runtime_code: Nullable<Bytes>;
165165
creation_transaction_hash: Nullable<Bytes>;
166166
}
167+
168+
export interface Signatures {
169+
signature_hash_32: BytesKeccak;
170+
signature_hash_4: Bytes;
171+
signature: string;
172+
}
173+
174+
export interface CompiledContractsSignatures {
175+
id: string;
176+
compilation_id: string;
177+
signature_hash_32: BytesKeccak;
178+
signature_type: "function" | "event" | "error";
179+
}
167180
}
168181

169182
export interface SourceInformation {
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { id as keccak256str, Fragment, JsonFragment } from "ethers";
2+
3+
export interface SignatureData {
4+
signature: string;
5+
signatureHash32: string;
6+
signatureType: "function" | "event" | "error";
7+
}
8+
9+
export function extractSignaturesFromAbi(abi: JsonFragment[]): SignatureData[] {
10+
const signatures: SignatureData[] = [];
11+
12+
for (const item of abi) {
13+
let fragment: Fragment;
14+
try {
15+
fragment = Fragment.from(item);
16+
} catch (error) {
17+
// Ignore invalid fragments
18+
// e.g. with custom type as they can appear in library ABIs
19+
continue;
20+
}
21+
switch (fragment.type) {
22+
case "function":
23+
case "event":
24+
case "error":
25+
signatures.push(getSignatureData(fragment));
26+
}
27+
}
28+
29+
return signatures;
30+
}
31+
32+
function getSignatureData(fragment: Fragment): SignatureData {
33+
const signature = fragment.format("sighash");
34+
return {
35+
signature,
36+
signatureHash32: keccak256str(signature),
37+
signatureType: fragment.type as "function" | "event" | "error",
38+
};
39+
}

services/server/test/helpers/helpers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,8 @@ export async function resetDatabase(sourcifyDatabase: Pool) {
291291
);
292292
await sourcifyDatabase.query("DELETE FROM verified_contracts");
293293
await sourcifyDatabase.query("DELETE FROM contract_deployments");
294+
await sourcifyDatabase.query("DELETE FROM compiled_contracts_signatures");
295+
await sourcifyDatabase.query("DELETE FROM signatures");
294296
await sourcifyDatabase.query("DELETE FROM compiled_contracts_sources");
295297
await sourcifyDatabase.query("DELETE FROM sources");
296298
await sourcifyDatabase.query("DELETE FROM compiled_contracts");

0 commit comments

Comments
 (0)