Skip to content

Commit

Permalink
also paginate mint burn history endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
ecioppettini committed Mar 5, 2024
1 parent 73b6941 commit c9180ba
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 319 deletions.
117 changes: 45 additions & 72 deletions docs/bin/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -894,6 +894,29 @@
},
"MintBurnSingleResponse": {
"properties": {
"block": {
"type": "string",
"description": "Block id of related mint / burn event",
"example": "4e90f1d14ad742a1c0e094a89ad180b896068f93fc3969614b1c53bac547b374",
"pattern": "[0-9a-fA-F]{64}"
},
"txId": {
"type": "string",
"description": "Transaction id of related mint / burn event",
"example": "28eb069e3e8c13831d431e3b2e35f58525493ab2d77fde83184993e4aa7a0eda",
"pattern": "[0-9a-fA-F]{64}"
},
"metadata": {
"type": "string",
"nullable": true,
"description": "Transaction metadata of related mint / burn event"
},
"actionSlot": {
"type": "number",
"format": "double",
"description": "Slot at which the transaction happened",
"example": 512345
},
"assets": {
"properties": {},
"additionalProperties": {
Expand All @@ -910,37 +933,14 @@
"42657272794e617679": "1"
}
}
},
"metadata": {
"type": "string",
"nullable": true,
"description": "Transaction metadata of related mint / burn event"
},
"actionBlockId": {
"type": "string",
"description": "Block id of related mint / burn event",
"example": "4e90f1d14ad742a1c0e094a89ad180b896068f93fc3969614b1c53bac547b374",
"pattern": "[0-9a-fA-F]{64}"
},
"actionTxId": {
"type": "string",
"description": "Transaction id of related mint / burn event",
"example": "28eb069e3e8c13831d431e3b2e35f58525493ab2d77fde83184993e4aa7a0eda",
"pattern": "[0-9a-fA-F]{64}"
},
"actionSlot": {
"type": "number",
"format": "double",
"description": "Slot at which the transaction happened",
"example": 512345
}
},
"required": [
"assets",
"block",
"txId",
"metadata",
"actionBlockId",
"actionTxId",
"actionSlot"
"actionSlot",
"assets"
],
"type": "object"
},
Expand All @@ -951,40 +951,30 @@
"type": "array"
},
"MintBurnHistoryRequest": {
"properties": {
"policyIds": {
"items": {
"$ref": "#/components/schemas/PolicyId"
},
"type": "array"
},
"range": {
"allOf": [
{
"properties": {
"maxSlot": {
"limit": {
"type": "number",
"format": "double",
"description": "Maximal slot from which the events should be returned (inclusive)",
"example": 46154860
"format": "double"
},
"minSlot": {
"type": "number",
"format": "double",
"description": "Minimal slot from which the events should be returned (not inclusive)",
"example": 46154769
"slotLimits": {
"$ref": "#/components/schemas/SlotLimits",
"description": "This limits the transactions in the result to this range of slots.\nEverything else is filtered out"
},
"policyIds": {
"items": {
"$ref": "#/components/schemas/PolicyId"
},
"type": "array"
}
},
"required": [
"maxSlot",
"minSlot"
],
"type": "object",
"description": "Mint Burn events in this slot range will be returned"
"type": "object"
},
{
"$ref": "#/components/schemas/Pagination"
}
},
"required": [
"range"
],
"type": "object"
]
},
"ProjectedNftStatus": {
"enum": [
Expand Down Expand Up @@ -1219,23 +1209,6 @@
"description": "Filter which uses of the address are considered relevant for the query.\n\nThis is a bitmask, so you can combine multiple options\nex: `RelationFilterType.Input & RelationFilterType.Output`\n\nNote: relations only apply to credentials and not to full bech32 addresses",
"pattern": "([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])"
},
"SlotLimits": {
"properties": {
"to": {
"type": "number",
"format": "double"
},
"from": {
"type": "number",
"format": "double"
}
},
"required": [
"to",
"from"
],
"type": "object"
},
"TransactionHistoryRequest": {
"allOf": [
{
Expand Down
199 changes: 93 additions & 106 deletions webserver/server/app/controllers/MintBurnHistoryController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@ import { StatusCodes } from 'http-status-codes';
import tx from 'pg-tx';
import pool from '../services/PgPoolSingleton';

import type { ErrorShape } from '../../../shared/errors';
import { Errors, genErrorMessage, type ErrorShape } from '../../../shared/errors';
import type { EndpointTypes } from '../../../shared/routes';
import { Routes } from '../../../shared/routes';
import { mintBurnRange, mintBurnRangeByPolicyIds } from '../services/MintBurnHistoryService';
import type { MintBurnSingleResponse } from '../../../shared/models/MintBurn';
import type { PolicyId } from '../../../shared/models/PolicyIdAssetMap';
import type {
ISqlMintBurnRangeResult,
ISqlMintBurnRangeByPolicyIdsResult,
} from '../models/asset/mintBurnHistory.queries';
import {
adjustToSlotLimits,
resolvePageStart,
resolveUntilTransaction,
} from '../services/PaginationService';
import { slotBoundsPagination } from '../models/pagination/slotBoundsPagination.queries';
import { MINT_BURN_HISTORY_LIMIT } from '../../../shared/constants';
import { expectType } from 'tsd';

const route = Routes.mintBurnHistory;

Expand All @@ -34,122 +41,102 @@ export class MintRangeController extends Controller {
ErrorShape
>
): Promise<EndpointTypes[typeof route]['response']> {
if (requestBody.policyIds !== undefined && requestBody.policyIds.length > 0) {
return await this.handle_by_policy_ids_query(requestBody.policyIds, requestBody);
} else {
return await this.handle_general_query(requestBody);
}
}
// note: we use a SQL transaction to make sure the pagination check works properly
// otherwise, a rollback could happen between getting the pagination info and the history query
const response = await tx<ErrorShape | MintBurnSingleResponse[]>(pool, async dbTx => {
const [until, pageStart, slotBounds] = await Promise.all([
resolveUntilTransaction({
block_hash: Buffer.from(requestBody.untilBlock, 'hex'),
dbTx,
}),
requestBody.after == null
? Promise.resolve(undefined)
: resolvePageStart({
after_block: Buffer.from(requestBody.after.block, 'hex'),
after_tx: Buffer.from(requestBody.after.tx, 'hex'),
dbTx,
}),
!requestBody.slotLimits
? Promise.resolve(undefined)
: slotBoundsPagination.run(
{ low: requestBody.slotLimits.from, high: requestBody.slotLimits.to },
dbTx
),
]);

if (until == null) {
return genErrorMessage(Errors.BlockHashNotFound, {
untilBlock: requestBody.untilBlock,
});
}
if (requestBody.after != null && pageStart == null) {
return genErrorMessage(Errors.PageStartNotFound, {
blockHash: requestBody.after.block,
txHash: requestBody.after.tx,
});
}

async handle_general_query(
requestBody: EndpointTypes[typeof route]['input']
): Promise<EndpointTypes[typeof route]['response']> {
const assets = await tx<ISqlMintBurnRangeResult[]>(pool, async dbTx => {
const data = await mintBurnRange({
range: requestBody.range,
dbTx,
const pageStartWithSlot = adjustToSlotLimits(
pageStart,
until,
requestBody.slotLimits,
slotBounds
);

const assets = await tx<ISqlMintBurnRangeResult[]>(pool, async dbTx => {
if (requestBody.policyIds !== undefined && requestBody.policyIds.length > 0) {
const data = await mintBurnRangeByPolicyIds({
after: pageStartWithSlot?.tx_id || 0,
until: until.tx_id,
limit: requestBody.limit || MINT_BURN_HISTORY_LIMIT.DEFAULT_PAGE_SIZE,
policyIds: requestBody.policyIds,
dbTx,
});

return data;
} else {
const data = await mintBurnRange({
after: pageStartWithSlot?.tx_id || 0,
until: until.tx_id,
limit: requestBody.limit || MINT_BURN_HISTORY_LIMIT.DEFAULT_PAGE_SIZE,
dbTx,
});

return data;
}
});

return data;
});

let mintRangeResponse: MintBurnSingleResponse = {
actionTxId: '',
actionBlockId: '',
metadata: null,
actionSlot: 0,
assets: {},
};

const result: MintBurnSingleResponse[] = [];

for (const entry of assets) {
const policyId = entry.policy_id !== null ? entry.policy_id.toString() : '';
const assetName = entry.asset_name !== null ? entry.asset_name.toString() : '';
const actionTxId = entry.action_tx_id !== null ? entry.action_tx_id.toString() : '';
const actionBlockId = entry.action_block_id !== null ? entry.action_block_id.toString() : '';

if (mintRangeResponse.actionTxId != actionTxId) {
if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
return assets.map(entry => {
const assets: { [policyId: PolicyId]: { [assetName: string]: string } } = {};

for (const pair of entry.payload as {
policyId: string;
assetName: string;
amount: string;
}[]) {
if (!assets[pair.policyId]) {
assets[pair.policyId] = { [pair.assetName]: pair.amount };
} else {
assets[pair.policyId][pair.assetName] = pair.amount;
}
}

mintRangeResponse = {
return {
assets: assets,
actionSlot: entry.action_slot,
actionTxId: actionTxId,
actionBlockId: actionBlockId,
metadata: entry.action_tx_metadata,
assets: {},
txId: entry.tx,
block: entry.block,
};
}

const for_policy = mintRangeResponse.assets[policyId] ?? {};

for_policy[assetName] = entry.amount;
mintRangeResponse.assets[policyId] = for_policy;
}

if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
}

return result;
}

async handle_by_policy_ids_query(
policyIds: PolicyId[],
requestBody: EndpointTypes[typeof route]['input']
): Promise<EndpointTypes[typeof route]['response']> {
const assets = await tx<ISqlMintBurnRangeByPolicyIdsResult[]>(pool, async dbTx => {
const data = await mintBurnRangeByPolicyIds({
range: requestBody.range,
policyIds: policyIds,
dbTx,
});

return data;
});

let mintRangeResponse: MintBurnSingleResponse = {
actionTxId: '',
actionBlockId: '',
metadata: null,
actionSlot: 0,
assets: {},
};

const result: MintBurnSingleResponse[] = [];

for (const entry of assets) {
const policyId = entry.policy_id !== null ? entry.policy_id.toString() : '';
const assetName = entry.asset_name !== null ? entry.asset_name.toString() : '';
const actionTxId = entry.action_tx_id !== null ? entry.action_tx_id.toString() : '';
const actionBlockId = entry.action_block_id !== null ? entry.action_block_id.toString() : '';

if (mintRangeResponse.actionTxId != actionTxId) {
if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
}

mintRangeResponse = {
actionSlot: entry.action_slot,
actionTxId: actionTxId,
actionBlockId: actionBlockId,
metadata: entry.action_tx_metadata,
assets: {},
};
}

const for_policy = mintRangeResponse.assets[policyId] ?? {};

for_policy[assetName] = entry.amount;
mintRangeResponse.assets[policyId] = for_policy;
}

if (mintRangeResponse.actionTxId.length > 0) {
result.push(mintRangeResponse);
if ('code' in response) {
expectType<Equals<typeof response, ErrorShape>>(true);
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return errorResponse(StatusCodes.CONFLICT, response);
}

return result;
return response;
}
}
Loading

0 comments on commit c9180ba

Please sign in to comment.