Skip to content

Commit

Permalink
feat: add config for minting backend (#1810)
Browse files Browse the repository at this point in the history
  • Loading branch information
shineli1984 authored May 21, 2024
1 parent 73460f8 commit 14d4120
Show file tree
Hide file tree
Showing 8 changed files with 431 additions and 274 deletions.
4 changes: 3 additions & 1 deletion packages/minting-backend/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"bugs": "https://github.com/immutable/ts-immutable-sdk/issues",
"dependencies": {
"@imtbl/blockchain-data": "0.0.0",
"@imtbl/config": "0.0.0"
"@imtbl/config": "0.0.0",
"@imtbl/metrics": "0.0.0",
"@imtbl/webhook": "0.0.0"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.0.0",
Expand Down
43 changes: 43 additions & 0 deletions packages/minting-backend/sdk/src/analytics/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { track } from '@imtbl/metrics';

const moduleName = 'minting_backend_sdk';

export const trackInitializePersistencePG = () => {
try {
track(moduleName, 'initializePersistencePG');
} catch {
// ignore
}
};

export const trackInitializePersistencePrismaSqlite = () => {
try {
track(moduleName, 'initializePersistencePrismaSqlite');
} catch {
// ignore
}
};

export const trackSubmitMintingRequests = () => {
try {
track(moduleName, 'submitMintingRequests');
} catch {
// ignore
}
};

export const trackProcessMint = () => {
try {
track(moduleName, 'processMint');
} catch {
// ignore
}
};

export const trackRecordMint = () => {
try {
track(moduleName, 'recordMint');
} catch {
// ignore
}
};
34 changes: 34 additions & 0 deletions packages/minting-backend/sdk/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* eslint-disable implicit-arrow-linebreak */
import {
ImmutableConfiguration,
ModuleConfiguration
} from '@imtbl/config';

import {
BlockchainData,
BlockchainDataModuleConfiguration
} from '@imtbl/blockchain-data';

import { MintingPersistence } from '../persistence/type';

export interface MintingBackendModuleParams { }

export interface MintingBackendModuleConfiguration
extends ModuleConfiguration<MintingBackendModuleParams> {
persistence: MintingPersistence;
blockchainDataModuleConfiguration: BlockchainDataModuleConfiguration;
}

export class MintingBackendConfiguration {
readonly baseConfig: ImmutableConfiguration;

readonly blockChainDataClient: BlockchainData;

readonly persistence: MintingPersistence;

constructor({ baseConfig, blockchainDataModuleConfiguration, persistence }: MintingBackendModuleConfiguration) {
this.baseConfig = baseConfig;
this.blockChainDataClient = new BlockchainData(blockchainDataModuleConfiguration);
this.persistence = persistence;
}
}
66 changes: 65 additions & 1 deletion packages/minting-backend/sdk/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,75 @@
import { ImmutableConfiguration, ModuleConfiguration } from '@imtbl/config';
import { BlockchainData } from '@imtbl/blockchain-data';
import { init } from '@imtbl/webhook';
import { mintingPersistence as mintingPersistencePg } from './persistence/pg/postgres';
import { mintingPersistence as mintingPersistencePrismaSqlite } from './persistence/prismaSqlite/sqlite';
import {
submitMintingRequests, processMint, recordMint
submitMintingRequests, processMint, recordMint,
MintRequestEvent
} from './minting';
import { CreateMintRequest, MintingPersistence } from './persistence/type';
import { Logger } from './logger/type';

export {
submitMintingRequests, processMint, recordMint,
// database clients
mintingPersistencePg, mintingPersistencePrismaSqlite
};

export interface MintingBackendModuleConfiguration
extends ModuleConfiguration<undefined> {
persistence: MintingPersistence;
logger?: Logger;
}

const noopHandlers = {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
zkevmMintRequestUpdated: async (event: MintRequestEvent) => { },
// eslint-disable-next-line @typescript-eslint/no-unused-vars
others: async (..._args: any) => { }
};

export class MintingBackendModule {
private readonly baseConfig: ImmutableConfiguration;

private readonly persistence: MintingPersistence;

private readonly blockchainDataClient: BlockchainData;

private readonly logger: Logger;

constructor(config: MintingBackendModuleConfiguration) {
this.baseConfig = config.baseConfig;
this.persistence = config.persistence;
this.logger = config.logger || console;
this.blockchainDataClient = new BlockchainData({
baseConfig: config.baseConfig
});
}

async recordMint(mintRequest: CreateMintRequest) {
await recordMint(this.persistence, mintRequest);
}

async submitMintingRequests(config: {
defaultBatchSize?: number;
chainName?: string;
maxNumberOfTries?: number;
}) {
await submitMintingRequests(
this.persistence,
this.blockchainDataClient,
config
);
}

async processMint(body: string | Record<string, unknown>, otherHandlers = noopHandlers) {
await init(body, this.baseConfig.environment, {
zkevmMintRequestUpdated: async (event: MintRequestEvent) => {
await processMint(this.persistence, event, this.logger);
otherHandlers.zkevmMintRequestUpdated(event);
},
others: otherHandlers.others
});
}
}
4 changes: 4 additions & 0 deletions packages/minting-backend/sdk/src/minting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BlockchainData as Types } from '@imtbl/generated-clients';
import { BlockchainData } from '@imtbl/blockchain-data';
import { CreateMintRequest, MintRequest, MintingPersistence } from './persistence/type';
import { Logger } from './logger/type';
import { trackProcessMint, trackRecordMint, trackSubmitMintingRequests } from './analytics';

// TODO: expose metrics
// - submitting status count, conflicting status count
Expand All @@ -14,6 +15,7 @@ export const recordMint = async (
mintingPersistence: MintingPersistence,
mintRequest: CreateMintRequest
) => {
trackRecordMint();
mintingPersistence.recordMint(mintRequest);
};

Expand All @@ -32,6 +34,7 @@ export const submitMintingRequests = async (
},
logger: Logger = console
) => {
trackSubmitMintingRequests();
let mintingResponse: Types.CreateMintRequestResult | undefined;
// eslint-disable-next-line no-constant-condition
while (true) {
Expand Down Expand Up @@ -214,6 +217,7 @@ export const processMint = async (
event: MintRequestEvent,
logger: Logger = console
) => {
trackProcessMint();
if (event.event_name !== 'imtbl_zkevm_mint_request_updated') {
logger.info(
`${event.event_name} is not imtbl_zkevm_mint_request_updated, skip.`
Expand Down
182 changes: 93 additions & 89 deletions packages/minting-backend/sdk/src/persistence/pg/postgres.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,98 @@
import type { Pool } from 'pg';
import { CreateMintRequest, MintingPersistence, SubmittedMintRequest } from '../type';
import { trackInitializePersistencePG } from '../../analytics';

export const mintingPersistence = (client: Pool): MintingPersistence => ({
recordMint: async (request: CreateMintRequest) => {
const r = await client.query(
`
INSERT INTO im_assets (asset_id, contract_address, owner_address, metadata)
VALUES ($1, $2, $3, $4) ON CONFLICT (asset_id, contract_address) DO NOTHING;
`,
[request.asset_id, request.contract_address, request.owner_address, request.metadata]
);
if (r.rowCount === 0) {
throw new Error('Duplicated mint');
}
},
getNextBatchForSubmission: async (limit: number) => {
const res = await client.query(`
UPDATE im_assets SET minting_status = 'submitting' WHERE minting_status IS NULL and id in (
select id from im_assets where minting_status is null limit $1 for update skip locked
) returning *;
`, [limit]);
return res.rows;
},
updateMintingStatusToSubmitted: async (ids: string[]) => {
await client.query(`
UPDATE im_assets SET minting_status = $2 WHERE id = ANY($1);
`, [ids, 'submitted']);
},
syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => {
// doing a upsert just in case the row has not been created yet
await client.query(`
INSERT INTO im_assets (
asset_id,
contract_address,
owner_address,
token_id,
minting_status,
metadata_id,
last_imtbl_zkevm_mint_request_updated_id,
error
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (asset_id, contract_address)
DO UPDATE SET
owner_address = $3,
token_id = $4,
minting_status = $5,
metadata_id = $6,
last_imtbl_zkevm_mint_request_updated_id = $7,
error = $8
where (
im_assets.last_imtbl_zkevm_mint_request_updated_id < $7 OR
im_assets.last_imtbl_zkevm_mint_request_updated_id is null
export const mintingPersistence = (client: Pool): MintingPersistence => {
trackInitializePersistencePG();
return {
recordMint: async (request: CreateMintRequest) => {
const r = await client.query(
`
INSERT INTO im_assets (asset_id, contract_address, owner_address, metadata)
VALUES ($1, $2, $3, $4) ON CONFLICT (asset_id, contract_address) DO NOTHING;
`,
[request.asset_id, request.contract_address, request.owner_address, request.metadata]
);
`, [
submittedMintRequest.assetId,
submittedMintRequest.contractAddress,
submittedMintRequest.ownerAddress,
submittedMintRequest.tokenId,
submittedMintRequest.status,
submittedMintRequest.metadataId,
submittedMintRequest.imtblZkevmMintRequestUpdatedId,
submittedMintRequest.error]);
},
markAsConflict: async (assetIds: string[], contractAddress: string) => {
await client.query(`
UPDATE im_assets
SET minting_status = 'conflicting'
WHERE asset_id = ANY($1)
AND contract_address = $2;
`, [assetIds, contractAddress]);
},
resetMintingStatus: async (ids: string[]) => {
await client.query(`
UPDATE im_assets SET minting_status = null WHERE id = ANY($1);
`, [ids]);
},
markForRetry: async (ids: string[]) => {
await client.query(`
UPDATE im_assets
SET minting_status = null, tried_count = tried_count + 1 WHERE id = ANY($1);
if (r.rowCount === 0) {
throw new Error('Duplicated mint');
}
},
getNextBatchForSubmission: async (limit: number) => {
const res = await client.query(`
UPDATE im_assets SET minting_status = 'submitting' WHERE minting_status IS NULL and id in (
select id from im_assets where minting_status is null limit $1 for update skip locked
) returning *;
`, [limit]);
return res.rows;
},
updateMintingStatusToSubmitted: async (ids: string[]) => {
await client.query(`
UPDATE im_assets SET minting_status = $2 WHERE id = ANY($1);
`, [ids, 'submitted']);
},
syncMintingStatus: async (submittedMintRequest: SubmittedMintRequest) => {
// doing a upsert just in case the row has not been created yet
await client.query(`
INSERT INTO im_assets (
asset_id,
contract_address,
owner_address,
token_id,
minting_status,
metadata_id,
last_imtbl_zkevm_mint_request_updated_id,
error
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) ON CONFLICT (asset_id, contract_address)
DO UPDATE SET
owner_address = $3,
token_id = $4,
minting_status = $5,
metadata_id = $6,
last_imtbl_zkevm_mint_request_updated_id = $7,
error = $8
where (
im_assets.last_imtbl_zkevm_mint_request_updated_id < $7 OR
im_assets.last_imtbl_zkevm_mint_request_updated_id is null
);
`, [
submittedMintRequest.assetId,
submittedMintRequest.contractAddress,
submittedMintRequest.ownerAddress,
submittedMintRequest.tokenId,
submittedMintRequest.status,
submittedMintRequest.metadataId,
submittedMintRequest.imtblZkevmMintRequestUpdatedId,
submittedMintRequest.error]);
},
markAsConflict: async (assetIds: string[], contractAddress: string) => {
await client.query(`
UPDATE im_assets
SET minting_status = 'conflicting'
WHERE asset_id = ANY($1)
AND contract_address = $2;
`, [assetIds, contractAddress]);
},
resetMintingStatus: async (ids: string[]) => {
await client.query(`
UPDATE im_assets SET minting_status = null WHERE id = ANY($1);
`, [ids]);
},
markForRetry: async (ids: string[]) => {
await client.query(`
UPDATE im_assets
SET minting_status = null, tried_count = tried_count + 1 WHERE id = ANY($1);
`, [ids]);
},
updateMintingStatusToSubmissionFailed: async (ids: string[]) => {
await client.query(`
UPDATE im_assets SET minting_status = 'submission_failed' WHERE id = ANY($1);
`, [ids]);
},
updateMintingStatusToSubmissionFailed: async (ids: string[]) => {
await client.query(`
UPDATE im_assets SET minting_status = 'submission_failed' WHERE id = ANY($1);
`, [ids]);
},
getMintingRequest: async (contractAddress: string, referenceId: string) => {
const res = await client.query(`
SELECT * FROM im_assets WHERE contract_address = $1 and asset_id = $2;
`, [contractAddress, referenceId]);
return res.rows[0] || null;
}
});
},
getMintingRequest: async (contractAddress: string, referenceId: string) => {
const res = await client.query(`
SELECT * FROM im_assets WHERE contract_address = $1 and asset_id = $2;
`, [contractAddress, referenceId]);
return res.rows[0] || null;
}
};
};
Loading

0 comments on commit 14d4120

Please sign in to comment.