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

Add /files/contracts/partial/{chain} endpoint and explicit ordering, resolve SQL NULL comparison bug #1416

Merged
merged 5 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ paths:
type: number
minimum: 1
maximum: 200
- name: order
in: query
required: false
schema:
type: string
enum: [asc, desc]
description: Order of the results. Default is "asc" (earliest verified contract first)
responses:
"200":
description: Chain is available as a full match or partial match in the repository
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ paths:
type: number
minimum: 1
maximum: 200
- name: order
in: query
required: false
schema:
type: string
enum: [asc, desc]
description: Order of the results. Default is "asc" (earliest verified contract first)

responses:
"200":
description: Chain is available as a full match in the repository
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
openapi: "3.0.0"

paths:
/files/contracts/partial/{chain}:
get:
summary: Get the contract addresses partially verified on a chain
description: Returns the partially verified contracts from the repository for the desired chain. API is paginated. Limit must be a number between 1 and 200.
tags:
- Repository
parameters:
- name: chain
in: path
required: true
schema:
type: string
format: sourcify-chainId
- name: page
in: query
required: false
schema:
type: number
- name: limit
in: query
required: false
schema:
type: number
minimum: 1
maximum: 200
- name: order
in: query
required: false
schema:
type: string
enum: [asc, desc]
description: Order of the results. Default is "asc" (earliest verified contract first)
responses:
"200":
description: Chain is available as a full match in the repository
content:
application/json:
schema:
type: object
properties:
results:
type: array
items:
type: string
example:
[
"0x1fE5d745beABA808AAdF52057Dd7AAA47b42cFD0",
"0xE9c31091868d68598Ac881738D159A63532d12f9",
]
pagination:
type: object
properties:
currentPage:
type: number
totalPages:
type: number
resultsPerPage:
type: number
resultsCurrentPage:
type: number
totalResults:
type: number
hasNextPage:
type: boolean
hasPreviousPage:
type: boolean
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ type PaginatedConractRetrieveMethod = (
chain: string,
match: MatchLevel,
page: number,
limit: number
limit: number,
descending: boolean
) => Promise<PaginatedContractData>;

export function createEndpoint(
Expand Down Expand Up @@ -75,7 +76,8 @@ export function createPaginatedContractEndpoint(
req.params.chain,
match,
parseInt((req.query.page as string) || "0"),
parseInt((req.query.limit as string) || "200")
parseInt((req.query.limit as string) || "200"),
req.query.order === "desc" // default is asc
);
} catch (err: any) {
return next(new NotFoundError(err.message));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,52 @@ const router: Router = Router();
{
prefix: "/contracts/full",
method: createPaginatedContractEndpoint(
(chain, match, page, limit) =>
services.storage.getPaginatedContracts(chain, match, page, limit),
(chain, match, page, limit, descending) =>
services.storage.getPaginatedContracts(
chain,
match,
page,
limit,
descending
),
"full_match"
),
},
{
prefix: "/contracts/partial",
method: createPaginatedContractEndpoint(
(chain, match, page, limit, descending) =>
services.storage.getPaginatedContracts(
chain,
match,
page,
limit,
descending
),
"partial_match"
),
},
{
prefix: "/contracts/any",
method: createPaginatedContractEndpoint(
(chain, match, page, limit) =>
services.storage.getPaginatedContracts(chain, match, page, limit),
(chain, match, page, limit, descending) =>
services.storage.getPaginatedContracts(
chain,
match,
page,
limit,
descending
),
"any_match"
),
},
{
prefix: "",
method: createEndpoint(
(chain, address, match) => services.storage.getContent(chain, address, match), "full_match"),
(chain, address, match) =>
services.storage.getContent(chain, address, match),
"full_match"
),
},
].forEach((pair) => {
router
Expand Down
7 changes: 5 additions & 2 deletions services/server/src/server/services/StorageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,21 +277,24 @@ export class StorageService {
chainId: string,
match: MatchLevel,
page: number,
limit: number
limit: number,
descending: boolean = false
): Promise<PaginatedContractData> => {
try {
return this.sourcifyDatabase!.getPaginatedContracts(
chainId,
match,
page,
limit
limit,
descending
);
} catch (error) {
logger.error("Error while getting paginated contracts from database", {
chainId,
match,
page,
limit,
descending,
error,
});
throw new Error("Error while getting paginated contracts from database");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export class SourcifyDatabaseService
}
);
throw new BadRequestError(
`Cannot fetch more than ${MAX_RETURNED_CONTRACTS_BY_GETCONTRACTS} contracts (${fullTotal} full matches, ${partialTotal} partial matches), please use /contracts/{full|any}/${chainId} with pagination`
`Cannot fetch more than ${MAX_RETURNED_CONTRACTS_BY_GETCONTRACTS} contracts (${fullTotal} full matches, ${partialTotal} partial matches), please use /contracts/{full|any|partial}/${chainId} with pagination`
);
}

Expand Down Expand Up @@ -198,7 +198,8 @@ export class SourcifyDatabaseService
chainId: string,
match: MatchLevel,
page: number,
limit: number
limit: number,
descending: boolean = false
): Promise<PaginatedContractData> => {
await this.init();

Expand Down Expand Up @@ -233,29 +234,31 @@ export class SourcifyDatabaseService
matchAddressesCountResult.rows[0].partial_total
);
const anyTotal = fullTotal + partialTotal;
if (match === "full_match") {
if (fullTotal === 0) {
return res;
}
res.pagination.totalResults = fullTotal;
} else if (match === "any_match") {
if (anyTotal === 0) {
return res;
}
res.pagination.totalResults = anyTotal;
const matchTotals: Record<MatchLevel, number> = {
full_match: fullTotal,
partial_match: partialTotal,
any_match: anyTotal,
};

// return empty res if requested `match` total is zero
if (matchTotals[match] === 0) {
return res;
}
res.pagination.totalResults = matchTotals[match];

res.pagination.totalPages = Math.ceil(
res.pagination.totalResults / res.pagination.resultsPerPage
);

// Now make the real query for addresses
const matchAddressesResult =
await Database.getSourcifyMatchAddressesByChainAndMatch(
this.databasePool,
parseInt(chainId),
match,
page,
limit
limit,
descending
);

if (matchAddressesResult.rowCount > 0) {
Expand Down
35 changes: 21 additions & 14 deletions services/server/src/server/services/utils/database-util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,17 +575,17 @@ export function normalizeRecompiledBytecodes(
export async function countSourcifyMatchAddresses(pool: Pool, chain: number) {
return await pool.query(
`
SELECT
contract_deployments.chain_id,
SUM(CASE WHEN sourcify_matches.creation_match = 'perfect' OR sourcify_matches.runtime_match = 'perfect' THEN 1 ELSE 0 END) AS full_total,
SUM(CASE WHEN sourcify_matches.creation_match != 'perfect' AND sourcify_matches.runtime_match != 'perfect' THEN 1 ELSE 0 END) AS partial_total
FROM sourcify_matches
JOIN verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id
JOIN contract_deployments ON
contract_deployments.id = verified_contracts.deployment_id
AND contract_deployments.chain_id = $1
GROUP BY contract_deployments.chain_id;
`,
SELECT
contract_deployments.chain_id,
SUM(CASE
WHEN COALESCE(sourcify_matches.creation_match, '') = 'perfect' OR sourcify_matches.runtime_match = 'perfect' THEN 1 ELSE 0 END) AS full_total,
SUM(CASE
WHEN COALESCE(sourcify_matches.creation_match, '') != 'perfect' AND sourcify_matches.runtime_match != 'perfect' THEN 1 ELSE 0 END) AS partial_total
FROM sourcify_matches
JOIN verified_contracts ON verified_contracts.id = sourcify_matches.verified_contract_id
JOIN contract_deployments ON contract_deployments.id = verified_contracts.deployment_id
WHERE contract_deployments.chain_id = $1
GROUP BY contract_deployments.chain_id;`,
[chain]
);
}
Expand All @@ -595,18 +595,19 @@ export async function getSourcifyMatchAddressesByChainAndMatch(
chain: number,
match: "full_match" | "partial_match" | "any_match",
page: number,
paginationSize: number
paginationSize: number,
descending: boolean = false
) {
let queryWhere = "";
switch (match) {
case "full_match": {
queryWhere =
"WHERE sourcify_matches.creation_match = 'perfect' OR sourcify_matches.runtime_match = 'perfect'";
"WHERE COALESCE(sourcify_matches.creation_match, '') = 'perfect' OR sourcify_matches.runtime_match = 'perfect'";
break;
}
case "partial_match": {
queryWhere =
"WHERE sourcify_matches.creation_match != 'perfect' AND sourcify_matches.runtime_match != 'perfect'";
"WHERE COALESCE(sourcify_matches.creation_match, '') != 'perfect' AND sourcify_matches.runtime_match != 'perfect'";
break;
}
case "any_match": {
Expand All @@ -617,6 +618,11 @@ export async function getSourcifyMatchAddressesByChainAndMatch(
throw new Error("Match type not supported");
}
}

const orderBy = descending
? "ORDER BY verified_contracts.id DESC"
: "ORDER BY verified_contracts.id ASC";

return await pool.query(
`
SELECT
Expand All @@ -627,6 +633,7 @@ export async function getSourcifyMatchAddressesByChainAndMatch(
contract_deployments.id = verified_contracts.deployment_id
AND contract_deployments.chain_id = $1
${queryWhere}
${orderBy}
OFFSET $2 LIMIT $3
`,
[chain, page * paginationSize, paginationSize]
Expand Down
4 changes: 2 additions & 2 deletions services/server/src/server/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// Types used internally by the server.

/**
* A type for specfifying the strictness level of querying (only full or any kind of matches)
* A type for specfifying the strictness level of querying (only full, partial or any kind of matches)
*/
export type MatchLevel = "full_match" | "any_match";
export type MatchLevel = "full_match" | "partial_match" | "any_match";

/**
* An array wrapper with info properties.
Expand Down
9 changes: 7 additions & 2 deletions services/server/test/helpers/LocalChainFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { LOCAL_CHAINS } from "../../src/sourcify-chains";
import nock from "nock";
import storageContractArtifact from "../testcontracts/Storage/Storage.json";
import storageContractMetadata from "../testcontracts/Storage/metadata.json";
import storageContractMetadataModified from "../testcontracts/Storage/metadataModified.json";

const storageContractSourcePath = path.join(
__dirname,
Expand All @@ -30,7 +31,10 @@ export class LocalChainFixture {
defaultContractMetadata = Buffer.from(
JSON.stringify(storageContractMetadata)
);
defaultContractModifiedIpfsMetadata = getModifiedIpfsMetadata();
defaultContractModifiedMetadata = Buffer.from(
JSON.stringify(storageContractMetadataModified)
);
defaultContractModifiedSourceIpfs = getModifiedSourceIpfs();
defaultContractArtifact = storageContractArtifact;

private _chainId?: string;
Expand Down Expand Up @@ -111,7 +115,8 @@ export class LocalChainFixture {
}
}

function getModifiedIpfsMetadata(): Buffer {
// Changes the IPFS hash inside the metadata file to make the source unfetchable
function getModifiedSourceIpfs(): Buffer {
const ipfsAddress =
storageContractMetadata.sources["project:/contracts/Storage.sol"].urls[1];
// change the last char in ipfs hash of the source file
Expand Down
1 change: 1 addition & 0 deletions services/server/test/helpers/ServerFixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export class ServerFixture {
beforeEach(async () => {
rimraf.sync(this.server.repository);
await resetDatabase(this.storageService);
console.log("Resetting the StorageService");
});

after(() => {
Expand Down
Loading