Skip to content

Commit

Permalink
feat: fees endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaelcr committed Jan 3, 2024
1 parent b64a131 commit 13946ea
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 3 deletions.
16 changes: 16 additions & 0 deletions src/api/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { createPox3EventsRouter } from './routes/pox3';
import { isPgConnectionError } from '../datastore/helpers';
import { createStackingRouter } from './routes/stacking';
import { logger, loggerMiddleware } from '../logger';
import { createMempoolRouter } from './v2/mempool';

export interface ApiServer {
expressApp: express.Express;
Expand Down Expand Up @@ -220,6 +221,21 @@ export async function startApiServer(opts: {
})()
);

app.use(
'/extended/v2',
(() => {
const router = express.Router();
router.use(cors());
router.use((req, res, next) => {
// Set caching on all routes to be disabled by default, individual routes can override
res.set('Cache-Control', 'no-store');
next();
});
router.use('/mempool', createMempoolRouter(datastore));
return router;
})()
);

app.use(
'/extended/beta',
(() => {
Expand Down
55 changes: 55 additions & 0 deletions src/api/v2/mempool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as express from 'express';
import { asyncHandler } from '../async-handler';
import {
ETagType,
getETagCacheHandler,
setETagCacheHeaders,
} from '../controllers/cache-controller';
import { PgStore } from '../../datastore/pg-store';
import { DbMempoolFeePriority, DbTxTypeId } from '../../datastore/common';
import { MempoolFeePriorities } from '../../../docs/generated';

function parseMempoolFeePriority(fees: DbMempoolFeePriority[]): MempoolFeePriorities {
const out: MempoolFeePriorities = {
all: { no_priority: 0, low_priority: 0, medium_priority: 0, high_priority: 0 },
};
for (const fee of fees) {
const value = {
no_priority: fee.no_priority,
low_priority: fee.low_priority,
medium_priority: fee.medium_priority,
high_priority: fee.high_priority,
};
if (fee.type_id == null) out.all = value;
else
switch (fee.type_id) {
case DbTxTypeId.TokenTransfer:
out.token_transfer = value;
break;
case DbTxTypeId.ContractCall:
out.contract_call = value;
break;
case DbTxTypeId.SmartContract:
case DbTxTypeId.VersionedSmartContract:
out.smart_contract = value;
break;
}
}
return out;
}

export function createMempoolRouter(db: PgStore): express.Router {
const router = express.Router();
const mempoolCacheHandler = getETagCacheHandler(db, ETagType.mempool);

router.get(
'/fees',
mempoolCacheHandler,
asyncHandler(async (req, res, next) => {
setETagCacheHeaders(res);
res.status(200).json(parseMempoolFeePriority(await db.getMempoolFeePriority()));
})
);

return router;
}
8 changes: 8 additions & 0 deletions src/datastore/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,14 @@ export interface DbMempoolStats {
>;
}

export interface DbMempoolFeePriority {
type_id: DbTxTypeId | null;
high_priority: number;
medium_priority: number;
low_priority: number;
no_priority: number;
}

export interface DbMempoolTx extends BaseTx {
pruned: boolean;

Expand Down
39 changes: 39 additions & 0 deletions src/datastore/pg-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
DbGetBlockWithMetadataOpts,
DbGetBlockWithMetadataResponse,
DbInboundStxTransfer,
DbMempoolFeePriority,
DbMempoolStats,
DbMempoolTx,
DbMicroblock,
Expand Down Expand Up @@ -1286,6 +1287,44 @@ export class PgStore {
};
}

async getMempoolFeePriority(): Promise<DbMempoolFeePriority[]> {
const txFeesQuery = await this.sql<DbMempoolFeePriority[]>`
WITH fees AS (
(
SELECT
NULL AS type_id,
ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY fee_rate ASC)) AS high_priority,
ROUND(PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY fee_rate ASC)) AS medium_priority,
ROUND(PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY fee_rate ASC)) AS low_priority,
ROUND(PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY fee_rate ASC)) AS no_priority
FROM mempool_txs
WHERE pruned = FALSE
)
UNION
(
WITH txs_grouped AS (
SELECT
(CASE type_id WHEN 6 THEN 1 ELSE type_id END) AS type_id,
fee_rate
FROM mempool_txs
WHERE pruned = FALSE
AND type_id NOT IN (4, 5)
)
SELECT
type_id,
ROUND(PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY fee_rate ASC)) AS high_priority,
ROUND(PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY fee_rate ASC)) AS medium_priority,
ROUND(PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY fee_rate ASC)) AS low_priority,
ROUND(PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY fee_rate ASC)) AS no_priority
FROM txs_grouped
GROUP BY type_id
)
)
SELECT * FROM fees ORDER BY type_id ASC NULLS FIRST
`;
return txFeesQuery;
}

async getMempoolTxList({
limit,
offset,
Expand Down
7 changes: 4 additions & 3 deletions src/test-utils/test-builders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
DbBnsNamespace,
DbEventTypeId,
DbFtEvent,
DbMempoolTx,
DbMempoolTxRaw,
DbMicroblockPartial,
DbMinerReward,
Expand Down Expand Up @@ -259,6 +258,8 @@ interface TestMempoolTxArgs {
smart_contract_contract_id?: string;
status?: DbTxStatus;
token_transfer_recipient_address?: string;
token_transfer_amount?: bigint;
token_transfer_memo?: string;
tx_id?: string;
type_id?: DbTxTypeId;
nonce?: number;
Expand Down Expand Up @@ -287,8 +288,8 @@ export function testMempoolTx(args?: TestMempoolTxArgs): DbMempoolTxRaw {
sponsor_address: undefined,
origin_hash_mode: 1,
sender_address: args?.sender_address ?? SENDER_ADDRESS,
token_transfer_amount: 1234n,
token_transfer_memo: '',
token_transfer_amount: args?.token_transfer_amount ?? 1234n,
token_transfer_memo: args?.token_transfer_memo ?? '',
token_transfer_recipient_address: args?.token_transfer_recipient_address ?? RECIPIENT_ADDRESS,
smart_contract_clarity_version: args?.smart_contract_clarity_version,
smart_contract_contract_id: args?.smart_contract_contract_id ?? CONTRACT_ID,
Expand Down
98 changes: 98 additions & 0 deletions src/tests/mempool-tests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1532,4 +1532,102 @@ describe('mempool tests', () => {
const txResult2 = await supertest(api.server).get(`/extended/v1/tx/${txId}`);
expect(txResult2.body.tx_status).toBe('success');
});

test('returns fee priorities for mempool transactions', async () => {
const mempoolTxs: DbMempoolTxRaw[] = [];
for (let i = 0; i < 10; i++) {
const sender_address = 'SP25YGP221F01S9SSCGN114MKDAK9VRK8P3KXGEMB';
const tx_id = `0x00000${i}`;
const fee_rate = BigInt(100000 * i);
const nonce = i;
if (i < 3) {
mempoolTxs.push({
tx_id,
nonce,
fee_rate,
type_id: DbTxTypeId.ContractCall,
anchor_mode: 3,
raw_tx: bufferToHexPrefixString(Buffer.from('test-raw-mempool-tx')),
status: 1,
post_conditions: '0x01f5',
sponsored: false,
sponsor_address: undefined,
contract_call_contract_id: 'SP32AEEF6WW5Y0NMJ1S8SBSZDAY8R5J32NBZFPKKZ.free-punks-v0',
contract_call_function_name: 'test-func',
contract_call_function_args: '0x00',
sender_address,
origin_hash_mode: 1,
pruned: false,
receipt_time: 1616063078,
});
} else if (i < 6) {
mempoolTxs.push({
tx_id,
nonce,
type_id: DbTxTypeId.SmartContract,
fee_rate,
anchor_mode: 3,
raw_tx: bufferToHexPrefixString(Buffer.from('test-raw-mempool-tx')),
status: 1,
post_conditions: '0x01f5',
sponsored: false,
sponsor_address: undefined,
sender_address,
origin_hash_mode: 1,
pruned: false,
smart_contract_contract_id: 'some-versioned-smart-contract',
smart_contract_source_code: '(some-versioned-contract-src)',
receipt_time: 1616063078,
});
} else {
mempoolTxs.push({
tx_id,
nonce,
type_id: DbTxTypeId.TokenTransfer,
fee_rate,
anchor_mode: 3,
raw_tx: bufferToHexPrefixString(Buffer.from('test-raw-mempool-tx')),
status: 1,
post_conditions: '0x01f5',
sponsored: false,
sponsor_address: undefined,
sender_address,
token_transfer_amount: 100n,
token_transfer_memo: '0x010101',
token_transfer_recipient_address: 'STB44HYPYAT2BB2QE513NSP81HTMYWBJP02HPGK6',
origin_hash_mode: 1,
pruned: false,
receipt_time: 1616063078,
});
}
}
await db.updateMempoolTxs({ mempoolTxs });
const result = await supertest(api.server).get(`/extended/v2/mempool/fees`);
expect(result.body).toBe({
all: {
high_priority: 855000,
low_priority: 450000,
medium_priority: 675000,
no_priority: 225000,
},
contract_call: {
high_priority: 190000,
low_priority: 100000,
medium_priority: 150000,
no_priority: 50000,
},
smart_contract: {
high_priority: 490000,
low_priority: 400000,
medium_priority: 450000,
no_priority: 350000,
},
token_transfer: {
high_priority: 885000,
low_priority: 750000,
medium_priority: 825000,
no_priority: 675000,
},
});
});
});

0 comments on commit 13946ea

Please sign in to comment.