Skip to content

Commit c389154

Browse files
authored
feat: ingest signer_signature from /new_block event and expose in new endpoint (#2125)
* feat: ingest `signer_signature` from `/new_block` event and expose in new endpoint * chore: lint fix * chore: fix tests * chore: signer-signature -> signer-signatures
1 parent 7d2f8df commit c389154

24 files changed

+307
-4
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* eslint-disable camelcase */
2+
3+
/** @param { import("node-pg-migrate").MigrationBuilder } pgm */
4+
exports.up = pgm => {
5+
6+
pgm.addColumn('blocks', {
7+
signer_signatures: {
8+
type: 'bytea[]',
9+
}
10+
});
11+
12+
pgm.createIndex('blocks', 'signer_signatures', { method: 'gin' });
13+
14+
};

src/api/pagination.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export enum ResourceType {
3939
Signer,
4040
PoxCycle,
4141
TokenHolders,
42+
BlockSignerSignature,
4243
}
4344

4445
export const pagingQueryLimits: Record<ResourceType, { defaultLimit: number; maxLimit: number }> = {
@@ -94,6 +95,10 @@ export const pagingQueryLimits: Record<ResourceType, { defaultLimit: number; max
9495
defaultLimit: 100,
9596
maxLimit: 200,
9697
},
98+
[ResourceType.BlockSignerSignature]: {
99+
defaultLimit: 500,
100+
maxLimit: 1000,
101+
},
97102
};
98103

99104
export function getPagingQueryLimit(

src/api/routes/v2/blocks.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@ import { Server } from 'node:http';
99
import { CursorOffsetParam, LimitParam, OffsetParam } from '../../schemas/params';
1010
import { getPagingQueryLimit, pagingQueryLimits, ResourceType } from '../../pagination';
1111
import { PaginatedResponse } from '../../schemas/util';
12-
import { NakamotoBlock, NakamotoBlockSchema } from '../../schemas/entities/block';
12+
import {
13+
NakamotoBlock,
14+
NakamotoBlockSchema,
15+
SignerSignatureSchema,
16+
} from '../../schemas/entities/block';
1317
import { TransactionSchema } from '../../schemas/entities/transactions';
14-
import { BlockListV2ResponseSchema } from '../../schemas/responses/responses';
18+
import {
19+
BlockListV2ResponseSchema,
20+
BlockSignerSignatureResponseSchema,
21+
} from '../../schemas/responses/responses';
1522

1623
export const BlockRoutesV2: FastifyPluginAsync<
1724
Record<never, never>,
@@ -174,5 +181,53 @@ export const BlockRoutesV2: FastifyPluginAsync<
174181
}
175182
);
176183

184+
fastify.get(
185+
'/:height_or_hash/signer-signatures',
186+
{
187+
preHandler: handleBlockCache,
188+
preValidation: (req, _reply, done) => {
189+
cleanBlockHeightOrHashParam(req.params);
190+
done();
191+
},
192+
schema: {
193+
operationId: 'get_signer_signatures_for_block',
194+
summary: 'Get signer signatures for block',
195+
description: `Retrieves the signer signatures (an array of signature byte strings) in a single block`,
196+
tags: ['Blocks'],
197+
params: BlockParamsSchema,
198+
querystring: Type.Object({
199+
limit: LimitParam(ResourceType.BlockSignerSignature),
200+
offset: OffsetParam(),
201+
}),
202+
response: {
203+
200: BlockSignerSignatureResponseSchema,
204+
},
205+
},
206+
},
207+
async (req, reply) => {
208+
const params = parseBlockParam(req.params.height_or_hash);
209+
const query = req.query;
210+
211+
try {
212+
const { limit, offset, results, total } = await fastify.db.v2.getBlockSignerSignature({
213+
blockId: params,
214+
...query,
215+
});
216+
const response = {
217+
limit,
218+
offset,
219+
total,
220+
results: results,
221+
};
222+
await reply.send(response);
223+
} catch (error) {
224+
if (error instanceof InvalidRequestError) {
225+
throw new NotFoundError('Block not found');
226+
}
227+
throw error;
228+
}
229+
}
230+
);
231+
177232
await Promise.resolve();
178233
};

src/api/routes/v2/schemas.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ export const TransactionLimitParamSchema = Type.Integer({
4747
description: 'Transactions per page',
4848
});
4949

50+
export const BlockSignerSignatureLimitParamSchema = Type.Integer({
51+
minimum: 1,
52+
maximum: pagingQueryLimits[ResourceType.BlockSignerSignature].maxLimit,
53+
default: pagingQueryLimits[ResourceType.BlockSignerSignature].defaultLimit,
54+
title: 'Block signer signature limit',
55+
description: 'Block signer signatures per page',
56+
});
57+
5058
export const PoxCycleLimitParamSchema = Type.Integer({
5159
minimum: 1,
5260
maximum: pagingQueryLimits[ResourceType.PoxCycle].maxLimit,

src/api/schemas/entities/block.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,7 @@ export const NakamotoBlockSchema = Type.Object({
116116
execution_cost_write_length: Type.Integer({ description: 'Execution cost write length.' }),
117117
});
118118
export type NakamotoBlock = Static<typeof NakamotoBlockSchema>;
119+
120+
export const SignerSignatureSchema = Type.String({
121+
description: "Array of hex strings representing the block's signer signature",
122+
});

src/api/schemas/responses/responses.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
BurnchainRewardSchema,
1313
BurnchainRewardSlotHolderSchema,
1414
} from '../entities/burnchain-rewards';
15-
import { NakamotoBlockSchema } from '../entities/block';
15+
import { NakamotoBlockSchema, SignerSignatureSchema } from '../entities/block';
1616

1717
export const ErrorResponseSchema = Type.Object(
1818
{
@@ -182,3 +182,6 @@ export type RunFaucetResponse = Static<typeof RunFaucetResponseSchema>;
182182

183183
export const BlockListV2ResponseSchema = PaginatedCursorResponse(NakamotoBlockSchema);
184184
export type BlockListV2Response = Static<typeof BlockListV2ResponseSchema>;
185+
186+
export const BlockSignerSignatureResponseSchema = PaginatedResponse(SignerSignatureSchema);
187+
export type BlockSignerSignatureResponse = Static<typeof BlockSignerSignatureResponseSchema>;

src/datastore/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface DbBlock {
2525
tx_count: number;
2626
block_time: number;
2727
signer_bitvec: string | null;
28+
signer_signatures: string[] | null;
2829
}
2930

3031
/** An interface representing the microblock data that can be constructed _only_ from the /new_microblocks payload */
@@ -1286,6 +1287,7 @@ export interface BlockInsertValues {
12861287
execution_cost_write_length: number;
12871288
tx_count: number;
12881289
signer_bitvec: string | null;
1290+
signer_signatures: PgBytea[] | null;
12891291
}
12901292

12911293
export interface MicroblockInsertValues {

src/datastore/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -487,6 +487,7 @@ export function parseBlockQueryResult(row: BlockQueryResult): DbBlock {
487487
execution_cost_write_length: Number.parseInt(row.execution_cost_write_length),
488488
tx_count: row.tx_count,
489489
signer_bitvec: row.signer_bitvec,
490+
signer_signatures: null, // this field is not queried from db by default due to size constraints
490491
};
491492
return block;
492493
}

src/datastore/pg-store-v2.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
PoxSignerPaginationQueryParams,
1616
PoxSignerLimitParamSchema,
1717
BlockIdParam,
18+
BlockSignerSignatureLimitParamSchema,
1819
} from '../api/routes/v2/schemas';
1920
import { InvalidRequestError, InvalidRequestErrorType } from '../errors';
2021
import { normalizeHashString } from '../helpers';
@@ -226,6 +227,48 @@ export class PgStoreV2 extends BasePgStoreModule {
226227
});
227228
}
228229

230+
async getBlockSignerSignature(args: {
231+
blockId: BlockIdParam;
232+
limit?: number;
233+
offset?: number;
234+
}): Promise<DbPaginatedResult<string>> {
235+
return await this.sqlTransaction(async sql => {
236+
const limit = args.limit ?? BlockSignerSignatureLimitParamSchema.default;
237+
const offset = args.offset ?? 0;
238+
const blockId = args.blockId;
239+
const filter =
240+
blockId.type === 'latest'
241+
? sql`index_block_hash = (SELECT index_block_hash FROM blocks WHERE canonical = TRUE ORDER BY block_height DESC LIMIT 1)`
242+
: blockId.type === 'hash'
243+
? sql`(
244+
block_hash = ${normalizeHashString(blockId.hash)}
245+
OR index_block_hash = ${normalizeHashString(blockId.hash)}
246+
)`
247+
: sql`block_height = ${blockId.height}`;
248+
const blockQuery = await sql<{ signer_signatures: string[]; total: number }[]>`
249+
SELECT
250+
signer_signatures[${offset + 1}:${offset + limit}] as signer_signatures,
251+
array_length(signer_signatures, 1)::integer AS total
252+
FROM blocks
253+
WHERE canonical = true AND ${filter}
254+
LIMIT 1
255+
`;
256+
if (blockQuery.count === 0)
257+
return {
258+
limit,
259+
offset,
260+
results: [],
261+
total: 0,
262+
};
263+
return {
264+
limit,
265+
offset,
266+
results: blockQuery[0].signer_signatures,
267+
total: blockQuery[0].total,
268+
};
269+
});
270+
}
271+
229272
async getAverageBlockTimes(): Promise<{
230273
last_1h: number;
231274
last_24h: number;

src/datastore/pg-write-store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ export class PgWriteStore extends PgStore {
484484
execution_cost_write_length: block.execution_cost_write_length,
485485
tx_count: block.tx_count,
486486
signer_bitvec: block.signer_bitvec,
487+
signer_signatures: block.signer_signatures,
487488
};
488489
const result = await sql`
489490
INSERT INTO blocks ${sql(values)}
@@ -3384,6 +3385,7 @@ export class PgWriteStore extends PgStore {
33843385
execution_cost_write_length: block.execution_cost_write_length,
33853386
tx_count: block.tx_count,
33863387
signer_bitvec: block.signer_bitvec,
3388+
signer_signatures: block.signer_signatures,
33873389
}));
33883390
await sql`
33893391
INSERT INTO blocks ${sql(values)}

0 commit comments

Comments
 (0)