Skip to content

Commit 852a60e

Browse files
alexthuthAlexander HuthDeveloperalexthuth
authored
feat: Add exclude_function_args parameter to reduce transaction response sizes (#2312)
* feat: implement exclude_function_args parameter for transaction endpoints - Add ExcludeFunctionArgsParamSchema to params.ts - Update parseContractCallMetadata to conditionally exclude function_args - Thread excludeFunctionArgs parameter through all parsing functions - Add parameter to transaction list, single tx, multiple tx, and mempool endpoints - Add parameter to address transaction endpoints - Add comprehensive unit tests following the testing strategy - Maintain 100% backward compatibility (defaults to false) - All lint and build checks pass Implements FEAT-FunctionArgsExclusion.md specification. * fix: parseContractCallMetadata function argument processing * feat: complete exclude_function_args implementation with all tests passing * feat: add exclude_function_args param to reduce response size * test: add parseContractCallMetadata missing function test * test: cover missing ABI function branch in parseContractCallMetadata * test: add focused test for missing ABI function coverage * chore: remove stray documentation files * chore: remove workplan file from PR * docs: update changelog for exclude_function_args feature * fix: conform test builders to DB constraints Default token_transfer_memo builders now default to '0x' (valid bytea hex). Set smartContract.clarity_version to null when undefined. Fixes bytea and UNDEFINED_VALUE errors in exclude_function_args tests. * feat: require excludeFunctionArgs param & drop 8.11.0 changelog - Param now mandatory; all call sites pass false - Restore CHANGELOG, remove 8.11.0 block only - Shorten schema description - Delete obsolete parse-contract-call-metadata test * chore: remove CHANGELOG.md from repository * feat: make excludeFunctionArgs required and update call sites - Restore CHANGELOG.md (auto-generated by semantic-release) - Make excludeFunctionArgs required in GetTxArgs and GetTxsArgs interfaces - Shorten description text in ExcludeFunctionArgsParamSchema - Pass excludeFunctionArgs: false at existing internal call sites and tests (per review guidance) * chore: fix prettier line length in tx tests (CI lint) --------- Co-authored-by: Alexander Huth <[email protected]> Co-authored-by: Developer <[email protected]> Co-authored-by: alexthuth <[email protected]>
1 parent b4ec932 commit 852a60e

File tree

15 files changed

+396
-59
lines changed

15 files changed

+396
-59
lines changed

src/api/controllers/db-controller.ts

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ export async function getRosettaBlockFromDataStore(
563563

564564
export async function getUnanchoredTxsFromDataStore(db: PgStore): Promise<Transaction[]> {
565565
const dbTxs = await db.getUnanchoredTxs();
566-
const parsedTxs = dbTxs.txs.map(dbTx => parseDbTx(dbTx));
566+
const parsedTxs = dbTxs.txs.map(dbTx => parseDbTx(dbTx, false));
567567
return parsedTxs;
568568
}
569569

@@ -864,6 +864,7 @@ export async function getRosettaTransactionFromDataStore(
864864
interface GetTxArgs {
865865
txId: string;
866866
includeUnanchored: boolean;
867+
excludeFunctionArgs: boolean;
867868
}
868869

869870
interface GetTxFromDbTxArgs extends GetTxArgs {
@@ -878,6 +879,7 @@ interface GetTxsWithEventsArgs extends GetTxsArgs {
878879
interface GetTxsArgs {
879880
txIds: string[];
880881
includeUnanchored: boolean;
882+
excludeFunctionArgs: boolean;
881883
}
882884

883885
interface GetTxWithEventsArgs extends GetTxArgs {
@@ -905,7 +907,10 @@ function parseDbBaseTx(dbTx: DbTx | DbMempoolTx): BaseTransaction {
905907
return tx;
906908
}
907909

908-
function parseDbTxTypeMetadata(dbTx: DbTx | DbMempoolTx): TransactionMetadata {
910+
function parseDbTxTypeMetadata(
911+
dbTx: DbTx | DbMempoolTx,
912+
excludeFunctionArgs: boolean
913+
): TransactionMetadata {
909914
switch (dbTx.type_id) {
910915
case DbTxTypeId.TokenTransfer: {
911916
const metadata: TokenTransferTransactionMetadata = {
@@ -965,7 +970,7 @@ function parseDbTxTypeMetadata(dbTx: DbTx | DbMempoolTx): TransactionMetadata {
965970
return metadata;
966971
}
967972
case DbTxTypeId.ContractCall: {
968-
return parseContractCallMetadata(dbTx);
973+
return parseContractCallMetadata(dbTx, excludeFunctionArgs);
969974
}
970975
case DbTxTypeId.PoisonMicroblock: {
971976
const metadata: PoisonMicroblockTransactionMetadata = {
@@ -1052,7 +1057,10 @@ function parseDbTxTypeMetadata(dbTx: DbTx | DbMempoolTx): TransactionMetadata {
10521057
}
10531058
}
10541059

1055-
export function parseContractCallMetadata(tx: BaseTx): ContractCallTransactionMetadata {
1060+
export function parseContractCallMetadata(
1061+
tx: BaseTx,
1062+
excludeFunctionArgs: boolean
1063+
): ContractCallTransactionMetadata {
10561064
const contractId = unwrapOptional(
10571065
tx.contract_call_contract_id,
10581066
() => 'Unexpected nullish contract_call_contract_id'
@@ -1063,6 +1071,7 @@ export function parseContractCallMetadata(tx: BaseTx): ContractCallTransactionMe
10631071
);
10641072
let functionAbi: ClarityAbiFunction | undefined;
10651073
const abi = tx.abi;
1074+
10661075
if (abi) {
10671076
const contractAbi: ClarityAbi = JSON.parse(abi);
10681077
functionAbi = contractAbi.functions.find(fn => fn.name === functionName);
@@ -1071,30 +1080,42 @@ export function parseContractCallMetadata(tx: BaseTx): ContractCallTransactionMe
10711080
}
10721081
}
10731082

1074-
const functionArgs = tx.contract_call_function_args
1075-
? decodeClarityValueList(tx.contract_call_function_args).map((c, fnArgIndex) => {
1076-
const functionArgAbi = functionAbi
1077-
? functionAbi.args[fnArgIndex++]
1078-
: { name: '', type: undefined };
1083+
const contractCall: {
1084+
contract_id: string;
1085+
function_name: string;
1086+
function_signature: string;
1087+
function_args?: {
1088+
hex: string;
1089+
repr: string;
1090+
name: string;
1091+
type: string;
1092+
}[];
1093+
} = {
1094+
contract_id: contractId,
1095+
function_name: functionName,
1096+
function_signature: functionAbi ? abiFunctionToString(functionAbi) : '',
1097+
};
1098+
1099+
// Only process function_args if not excluded
1100+
if (!excludeFunctionArgs && tx.contract_call_function_args) {
1101+
contractCall.function_args = decodeClarityValueList(tx.contract_call_function_args).map(
1102+
(c, idx) => {
1103+
const functionArgAbi = functionAbi ? functionAbi.args[idx] : { name: '', type: undefined };
10791104
return {
10801105
hex: c.hex,
10811106
repr: c.repr,
1082-
name: functionArgAbi.name,
1083-
type: functionArgAbi.type
1107+
name: functionArgAbi?.name || '',
1108+
type: functionArgAbi?.type
10841109
? getTypeString(functionArgAbi.type)
10851110
: decodeClarityValueToTypeName(c.hex),
10861111
};
1087-
})
1088-
: undefined;
1112+
}
1113+
);
1114+
}
10891115

10901116
const metadata: ContractCallTransactionMetadata = {
10911117
tx_type: 'contract_call',
1092-
contract_call: {
1093-
contract_id: contractId,
1094-
function_name: functionName,
1095-
function_signature: functionAbi ? abiFunctionToString(functionAbi) : '',
1096-
function_args: functionArgs,
1097-
},
1118+
contract_call: contractCall,
10981119
};
10991120
return metadata;
11001121
}
@@ -1154,21 +1175,24 @@ function parseDbAbstractMempoolTx(
11541175
return abstractMempoolTx;
11551176
}
11561177

1157-
export function parseDbTx(dbTx: DbTx): Transaction {
1178+
export function parseDbTx(dbTx: DbTx, excludeFunctionArgs: boolean): Transaction {
11581179
const baseTx = parseDbBaseTx(dbTx);
11591180
const abstractTx = parseDbAbstractTx(dbTx, baseTx);
1160-
const txMetadata = parseDbTxTypeMetadata(dbTx);
1181+
const txMetadata = parseDbTxTypeMetadata(dbTx, excludeFunctionArgs);
11611182
const result: Transaction = {
11621183
...abstractTx,
11631184
...txMetadata,
11641185
};
11651186
return result;
11661187
}
11671188

1168-
export function parseDbMempoolTx(dbMempoolTx: DbMempoolTx): MempoolTransaction {
1189+
export function parseDbMempoolTx(
1190+
dbMempoolTx: DbMempoolTx,
1191+
excludeFunctionArgs: boolean
1192+
): MempoolTransaction {
11691193
const baseTx = parseDbBaseTx(dbMempoolTx);
11701194
const abstractTx = parseDbAbstractMempoolTx(dbMempoolTx, baseTx);
1171-
const txMetadata = parseDbTxTypeMetadata(dbMempoolTx);
1195+
const txMetadata = parseDbTxTypeMetadata(dbMempoolTx, excludeFunctionArgs);
11721196
const result: MempoolTransaction = {
11731197
...abstractTx,
11741198
...txMetadata,
@@ -1189,7 +1213,9 @@ export async function getMempoolTxsFromDataStore(
11891213
return [];
11901214
}
11911215

1192-
const parsedMempoolTxs = mempoolTxsQuery.map(tx => parseDbMempoolTx(tx));
1216+
const parsedMempoolTxs = mempoolTxsQuery.map(tx =>
1217+
parseDbMempoolTx(tx, args.excludeFunctionArgs)
1218+
);
11931219

11941220
return parsedMempoolTxs;
11951221
}
@@ -1211,7 +1237,7 @@ async function getTxsFromDataStore(
12111237
}
12121238

12131239
// parsing txQuery
1214-
const parsedTxs = txQuery.map(tx => parseDbTx(tx));
1240+
const parsedTxs = txQuery.map(tx => parseDbTx(tx, args.excludeFunctionArgs));
12151241

12161242
// incase transaction events are requested
12171243
if ('eventLimit' in args) {
@@ -1261,7 +1287,7 @@ export async function getTxFromDataStore(
12611287
dbTx = txQuery.result;
12621288
}
12631289

1264-
const parsedTx = parseDbTx(dbTx);
1290+
const parsedTx = parseDbTx(dbTx, args.excludeFunctionArgs);
12651291

12661292
// If tx events are requested
12671293
if ('eventLimit' in args) {
@@ -1315,6 +1341,7 @@ export async function searchTxs(
13151341
const mempoolTxsQuery = await getMempoolTxsFromDataStore(db, {
13161342
txIds: mempoolTxs,
13171343
includeUnanchored: args.includeUnanchored,
1344+
excludeFunctionArgs: args.excludeFunctionArgs,
13181345
});
13191346

13201347
// merging found mempool transaction in found transactions object

src/api/routes/address.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
PrincipalSchema,
3434
UnanchoredParamSchema,
3535
UntilBlockSchema,
36+
ExcludeFunctionArgsParamSchema,
3637
} from '../schemas/params';
3738
import {
3839
AddressBalance,
@@ -290,6 +291,7 @@ export const AddressRoutes: FastifyPluginAsync<
290291
),
291292
unanchored: UnanchoredParamSchema,
292293
until_block: UntilBlockSchema,
294+
exclude_function_args: ExcludeFunctionArgsParamSchema,
293295
}),
294296
response: {
295297
200: AddressTransactionsListResponseSchema,
@@ -302,6 +304,7 @@ export const AddressRoutes: FastifyPluginAsync<
302304
const untilBlock = parseUntilBlockQuery(req.query.until_block, req.query.unanchored);
303305
const limit = getPagingQueryLimit(ResourceType.Tx, req.query.limit);
304306
const offset = req.query.offset ?? 0;
307+
const excludeFunctionArgs = req.query.exclude_function_args ?? false;
305308

306309
const response = await fastify.db.sqlTransaction(async sql => {
307310
const blockParams = getBlockParams(req.query.height, req.query.unanchored);
@@ -327,7 +330,7 @@ export const AddressRoutes: FastifyPluginAsync<
327330
blockHeight,
328331
atSingleBlock,
329332
});
330-
const results = txResults.map(dbTx => parseDbTx(dbTx));
333+
const results = txResults.map(dbTx => parseDbTx(dbTx, excludeFunctionArgs));
331334
const response = { limit, offset, total, results };
332335
return response;
333336
});
@@ -376,6 +379,7 @@ export const AddressRoutes: FastifyPluginAsync<
376379
txId: results.tx.tx_id,
377380
dbTx: results.tx,
378381
includeUnanchored: false,
382+
excludeFunctionArgs: false,
379383
});
380384
if (!txQuery.found) {
381385
throw new Error('unexpected tx not found -- fix tx enumeration query');
@@ -468,6 +472,7 @@ export const AddressRoutes: FastifyPluginAsync<
468472
txId: entry.tx.tx_id,
469473
dbTx: entry.tx,
470474
includeUnanchored: blockParams.includeUnanchored ?? false,
475+
excludeFunctionArgs: false,
471476
});
472477
if (!txQuery.found) {
473478
throw new Error('unexpected tx not found -- fix tx enumeration query');
@@ -671,6 +676,7 @@ export const AddressRoutes: FastifyPluginAsync<
671676
limit: LimitParam(ResourceType.Tx),
672677
offset: OffsetParam(),
673678
unanchored: UnanchoredParamSchema,
679+
exclude_function_args: ExcludeFunctionArgsParamSchema,
674680
}),
675681
response: {
676682
200: PaginatedResponse(MempoolTransactionSchema, {
@@ -690,13 +696,16 @@ export const AddressRoutes: FastifyPluginAsync<
690696
);
691697
}
692698
const includeUnanchored = req.query.unanchored ?? false;
699+
const excludeFunctionArgs = req.query.exclude_function_args ?? false;
693700
const { results: txResults, total } = await fastify.db.getMempoolTxList({
694701
offset,
695702
limit,
696703
address,
697704
includeUnanchored,
698705
});
699-
const results: MempoolTransaction[] = txResults.map(tx => parseDbMempoolTx(tx));
706+
const results: MempoolTransaction[] = txResults.map(tx =>
707+
parseDbMempoolTx(tx, excludeFunctionArgs)
708+
);
700709
const response = { limit, offset, total, results };
701710
await reply.send(response);
702711
}

src/api/routes/block.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ export const BlockRoutes: FastifyPluginAsync<
159159
if (!block.found) {
160160
throw new NotFoundError(`cannot find block by hash`);
161161
}
162+
162163
await reply.send(block.result);
163164
}
164165
);

src/api/routes/search.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export const SearchRoutes: FastifyPluginAsync<
114114
},
115115
};
116116
if (includeMetadata) {
117-
txResult.metadata = parseDbTx(txData);
117+
txResult.metadata = parseDbTx(txData, false);
118118
}
119119
return { found: true, result: txResult };
120120
} else if (queryResult.result.entity_type === 'mempool_tx_id') {
@@ -127,7 +127,7 @@ export const SearchRoutes: FastifyPluginAsync<
127127
},
128128
};
129129
if (includeMetadata) {
130-
txResult.metadata = parseDbMempoolTx(txData);
130+
txResult.metadata = parseDbMempoolTx(txData, false);
131131
}
132132
return { found: true, result: txResult };
133133
} else {
@@ -176,7 +176,7 @@ export const SearchRoutes: FastifyPluginAsync<
176176
},
177177
};
178178
if (includeMetadata) {
179-
contractResult.metadata = parseDbTx(txData);
179+
contractResult.metadata = parseDbTx(txData, false);
180180
}
181181
return { found: true, result: contractResult };
182182
} else {
@@ -191,7 +191,7 @@ export const SearchRoutes: FastifyPluginAsync<
191191
},
192192
};
193193
if (includeMetadata) {
194-
contractResult.metadata = parseDbMempoolTx(txData);
194+
contractResult.metadata = parseDbMempoolTx(txData, false);
195195
}
196196
return { found: true, result: contractResult };
197197
}

src/api/routes/tokens.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export const TokenRoutes: FastifyPluginAsync<
116116
if (includeTxMetadata && result.tx) {
117117
return {
118118
...parsedNftData,
119-
tx: parseDbTx(result.tx),
119+
tx: parseDbTx(result.tx, false),
120120
};
121121
}
122122
return { ...parsedNftData, tx_id: result.nft_holding_info.tx_id };
@@ -225,7 +225,7 @@ export const TokenRoutes: FastifyPluginAsync<
225225
if (includeTxMetadata && result.tx) {
226226
return {
227227
...parsedNftData,
228-
tx: parseDbTx(result.tx),
228+
tx: parseDbTx(result.tx, false),
229229
};
230230
}
231231
return { ...parsedNftData, tx_id: result.nft_event.tx_id };
@@ -331,7 +331,7 @@ export const TokenRoutes: FastifyPluginAsync<
331331
if (includeTxMetadata && result.tx) {
332332
return {
333333
...parsedNftData,
334-
tx: parseDbTx(result.tx),
334+
tx: parseDbTx(result.tx, false),
335335
};
336336
}
337337
return { ...parsedNftData, tx_id: result.nft_event.tx_id };

0 commit comments

Comments
 (0)