Skip to content

Commit f663d97

Browse files
committed
feat: tx list function name fuzzy search
1 parent 66e6800 commit f663d97

File tree

4 files changed

+112
-0
lines changed

4 files changed

+112
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/** @param { import("node-pg-migrate").MigrationBuilder } pgm */
2+
exports.up = pgm => {
3+
pgm.sql('CREATE EXTENSION IF NOT EXISTS pg_trgm;');
4+
5+
pgm.sql(`
6+
CREATE INDEX idx_contract_call_function_name_trgm
7+
ON txs
8+
USING gin (contract_call_function_name gin_trgm_ops);
9+
`);
10+
};
11+
12+
/** @param { import("node-pg-migrate").MigrationBuilder } pgm */
13+
exports.down = pgm => {
14+
pgm.sql(`
15+
DROP INDEX IF EXISTS idx_contract_call_function_name_trgm;
16+
`);
17+
18+
pgm.sql('DROP EXTENSION IF EXISTS pg_trgm;');
19+
};

src/api/routes/tx.ts

+12
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ export const TxRoutes: FastifyPluginAsync<
113113
examples: [1706745599],
114114
})
115115
),
116+
search_term: Type.Optional(
117+
Type.String({
118+
description: 'Option to search for transactions by a search term',
119+
examples: ['swap'],
120+
})
121+
),
116122
contract_id: Type.Optional(
117123
Type.String({
118124
description: 'Option to filter results by contract ID',
@@ -178,6 +184,11 @@ export const TxRoutes: FastifyPluginAsync<
178184
contractId = req.query.contract_id;
179185
}
180186

187+
let searchTerm: string | undefined;
188+
if (typeof req.query.search_term === 'string') {
189+
searchTerm = req.query.search_term;
190+
}
191+
181192
const { results: txResults, total } = await fastify.db.getTxList({
182193
offset,
183194
limit,
@@ -188,6 +199,7 @@ export const TxRoutes: FastifyPluginAsync<
188199
startTime: req.query.start_time,
189200
endTime: req.query.end_time,
190201
contractId,
202+
searchTerm,
191203
functionName: req.query.function_name,
192204
nonce: req.query.nonce,
193205
order: req.query.order,

src/datastore/pg-store.ts

+11
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ import { parseBlockParam } from '../api/routes/v2/schemas';
101101

102102
export const MIGRATIONS_DIR = path.join(REPO_DIR, 'migrations');
103103

104+
const TRGM_SIMILARITY_THRESHOLD = 0.3;
105+
104106
/**
105107
* This is the main interface between the API and the Postgres database. It contains all methods that
106108
* query the DB in search for blockchain data to be returned via endpoints or WebSockets/Socket.IO.
@@ -1416,6 +1418,7 @@ export class PgStore extends BasePgStore {
14161418
startTime,
14171419
endTime,
14181420
contractId,
1421+
searchTerm,
14191422
functionName,
14201423
nonce,
14211424
order,
@@ -1430,6 +1433,7 @@ export class PgStore extends BasePgStore {
14301433
startTime?: number;
14311434
endTime?: number;
14321435
contractId?: string;
1436+
searchTerm?: string;
14331437
functionName?: string;
14341438
nonce?: number;
14351439
order?: 'desc' | 'asc';
@@ -1468,6 +1472,10 @@ export class PgStore extends BasePgStore {
14681472
const contractIdFilterSql = contractId
14691473
? sql`AND contract_call_contract_id = ${contractId}`
14701474
: sql``;
1475+
1476+
const searchTermFilterSql = searchTerm
1477+
? sql`AND similarity(contract_call_function_name, ${searchTerm}) > ${TRGM_SIMILARITY_THRESHOLD}`
1478+
: sql``;
14711479
const contractFuncFilterSql = functionName
14721480
? sql`AND contract_call_function_name = ${functionName}`
14731481
: sql``;
@@ -1479,6 +1487,7 @@ export class PgStore extends BasePgStore {
14791487
!startTime &&
14801488
!endTime &&
14811489
!contractId &&
1490+
!searchTerm &&
14821491
!functionName &&
14831492
!nonce;
14841493

@@ -1497,6 +1506,7 @@ export class PgStore extends BasePgStore {
14971506
${startTimeFilterSql}
14981507
${endTimeFilterSql}
14991508
${contractIdFilterSql}
1509+
${searchTermFilterSql}
15001510
${contractFuncFilterSql}
15011511
${nonceFilterSql}
15021512
`;
@@ -1511,6 +1521,7 @@ export class PgStore extends BasePgStore {
15111521
${startTimeFilterSql}
15121522
${endTimeFilterSql}
15131523
${contractIdFilterSql}
1524+
${searchTermFilterSql}
15141525
${contractFuncFilterSql}
15151526
${nonceFilterSql}
15161527
${orderBySql}

tests/api/tx.test.ts

+70
Original file line numberDiff line numberDiff line change
@@ -2322,6 +2322,76 @@ describe('tx tests', () => {
23222322
);
23232323
});
23242324

2325+
test('tx list - filter by searchTerm using trigram', async () => {
2326+
const transferTokenTx = {
2327+
tx_id: '0x1111',
2328+
contract_call_function_name: 'transferToken',
2329+
};
2330+
2331+
const stakeTokenTx = {
2332+
tx_id: '0x2222',
2333+
contract_call_function_name: 'stakeToken',
2334+
};
2335+
2336+
const burnTokenTx = {
2337+
tx_id: '0x3333',
2338+
contract_call_function_name: 'burnToken',
2339+
}
2340+
2341+
const block1 = new TestBlockBuilder({ block_height: 1, index_block_hash: '0x01' })
2342+
.addTx(transferTokenTx)
2343+
.build();
2344+
await db.update(block1);
2345+
2346+
const block2 = new TestBlockBuilder({
2347+
block_height: 2,
2348+
index_block_hash: '0x02',
2349+
parent_block_hash: block1.block.block_hash,
2350+
parent_index_block_hash: block1.block.index_block_hash,
2351+
})
2352+
.addTx(stakeTokenTx)
2353+
.addTx(burnTokenTx)
2354+
.build();
2355+
await db.update(block2);
2356+
2357+
const searchTerm = 'transfer';
2358+
2359+
const txsReq = await supertest(api.server).get(`/extended/v1/tx?search_term=${searchTerm}`);
2360+
expect(txsReq.status).toBe(200);
2361+
expect(txsReq.body).toEqual(
2362+
expect.objectContaining({
2363+
results: [
2364+
expect.objectContaining({
2365+
tx_id: transferTokenTx.tx_id,
2366+
}),
2367+
],
2368+
})
2369+
);
2370+
2371+
const broadSearchTerm = 'token';
2372+
2373+
const txsReqBroad = await supertest(api.server).get(
2374+
`/extended/v1/tx?search_term=${broadSearchTerm}`
2375+
);
2376+
expect(txsReqBroad.status).toBe(200);
2377+
2378+
expect(txsReqBroad.body).toEqual(
2379+
expect.objectContaining({
2380+
results: [
2381+
expect.objectContaining({
2382+
tx_id: burnTokenTx.tx_id,
2383+
}),
2384+
expect.objectContaining({
2385+
tx_id: stakeTokenTx.tx_id,
2386+
}),
2387+
expect.objectContaining({
2388+
tx_id: transferTokenTx.tx_id,
2389+
}),
2390+
],
2391+
})
2392+
);
2393+
});
2394+
23252395
test('tx list - filter by contract id/name', async () => {
23262396
const testContractAddr = 'ST27W5M8BRKA7C5MZE2R1S1F4XTPHFWFRNHA9M04Y.hello-world';
23272397
const testContractFnName = 'test-contract-fn';

0 commit comments

Comments
 (0)