Skip to content

Commit 34a8454

Browse files
zone117xsemantic-release-botcsgui
authored
fix: ensure events are inserted into the raw event request table (#1925)
* feat: ensure events are inserted into the raw event request table otherwise explicit error * chore: fix a few missing JSON.parse calls * fix: do not string escape the json when inserting into event_observer_requests * fix: event-server requested ending incorrectly * fix: revert back to passing json object to avoid escaping issues with sql.unsafe() * fix: fix drop_mempool_tx handler --------- Co-authored-by: semantic-release-bot <[email protected]> Co-authored-by: Chris Guimaraes <[email protected]>
1 parent 2c572f2 commit 34a8454

File tree

3 files changed

+105
-49
lines changed

3 files changed

+105
-49
lines changed

src/datastore/pg-write-store.ts

Lines changed: 19 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -176,27 +176,25 @@ export class PgWriteStore extends PgStore {
176176
return store;
177177
}
178178

179-
async storeRawEventRequest(eventPath: string, payload: PgJsonb): Promise<void> {
180-
// To avoid depending on the DB more than once and to allow the query transaction to settle,
181-
// we'll take the complete insert result and move that to the output TSV file instead of taking
182-
// only the `id` and performing a `COPY` of that row later.
183-
const insertResult = await this.sql<
184-
{
185-
id: string;
186-
receive_timestamp: string;
187-
event_path: string;
188-
payload: string;
189-
}[]
190-
>`INSERT INTO event_observer_requests(
191-
event_path, payload
192-
) values(${eventPath}, ${payload})
193-
RETURNING id, receive_timestamp::text, event_path, payload::text
194-
`;
195-
if (insertResult.length !== 1) {
196-
throw new Error(
197-
`Unexpected row count ${insertResult.length} when storing event_observer_requests entry`
198-
);
199-
}
179+
async storeRawEventRequest(eventPath: string, payload: any): Promise<void> {
180+
await this.sqlWriteTransaction(async sql => {
181+
const insertResult = await sql<
182+
{
183+
id: string;
184+
receive_timestamp: string;
185+
event_path: string;
186+
}[]
187+
>`INSERT INTO event_observer_requests(
188+
event_path, payload
189+
) values(${eventPath}, ${payload})
190+
RETURNING id, receive_timestamp::text, event_path
191+
`;
192+
if (insertResult.length !== 1) {
193+
throw new Error(
194+
`Unexpected row count ${insertResult.length} when storing event_observer_requests entry`
195+
);
196+
}
197+
});
200198
}
201199

202200
async update(data: DataStoreBlockUpdateData): Promise<void> {

src/event-stream/event-server.ts

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ export async function startEventServer(opts: {
926926

927927
const app = express();
928928

929-
const handleRawEventRequest = asyncHandler(async req => {
929+
const handleRawEventRequest = async (req: express.Request) => {
930930
await messageHandler.handleRawEventRequest(req.path, req.body, db);
931931

932932
if (logger.level === 'debug') {
@@ -938,10 +938,9 @@ export async function startEventServer(opts: {
938938
}
939939
logger.debug(`${eventPath} ${payload}`, { component: 'stacks-node-event' });
940940
}
941-
});
941+
};
942942

943943
app.use(loggerMiddleware);
944-
945944
app.use(bodyParser.json({ type: 'application/json', limit: '500MB' }));
946945

947946
const ibdHeight = getIbdBlockHeight();
@@ -952,7 +951,7 @@ export async function startEventServer(opts: {
952951
if (chainTip.block_height > ibdHeight) {
953952
next();
954953
} else {
955-
handleRawEventRequest(req, res, next);
954+
await handleRawEventRequest(req);
956955
res.status(200).send(`IBD`);
957956
}
958957
} catch (error) {
@@ -971,101 +970,95 @@ export async function startEventServer(opts: {
971970

972971
app.post(
973972
'/new_block',
974-
asyncHandler(async (req, res, next) => {
973+
asyncHandler(async (req, res) => {
975974
try {
976975
const blockMessage: CoreNodeBlockMessage = req.body;
977976
await messageHandler.handleBlockMessage(opts.chainId, blockMessage, db);
978977
if (blockMessage.block_height === 1) {
979978
await handleBnsImport(db);
980979
}
980+
await handleRawEventRequest(req);
981981
res.status(200).json({ result: 'ok' });
982-
next();
983982
} catch (error) {
984983
logger.error(error, 'error processing core-node /new_block');
985984
res.status(500).json({ error: error });
986985
}
987-
}),
988-
handleRawEventRequest
986+
})
989987
);
990988

991989
app.post(
992990
'/new_burn_block',
993-
asyncHandler(async (req, res, next) => {
991+
asyncHandler(async (req, res) => {
994992
try {
995993
const msg: CoreNodeBurnBlockMessage = req.body;
996994
await messageHandler.handleBurnBlock(msg, db);
995+
await handleRawEventRequest(req);
997996
res.status(200).json({ result: 'ok' });
998-
next();
999997
} catch (error) {
1000998
logger.error(error, 'error processing core-node /new_burn_block');
1001999
res.status(500).json({ error: error });
10021000
}
1003-
}),
1004-
handleRawEventRequest
1001+
})
10051002
);
10061003

10071004
app.post(
10081005
'/new_mempool_tx',
1009-
asyncHandler(async (req, res, next) => {
1006+
asyncHandler(async (req, res) => {
10101007
try {
10111008
const rawTxs: string[] = req.body;
10121009
await messageHandler.handleMempoolTxs(rawTxs, db);
1010+
await handleRawEventRequest(req);
10131011
res.status(200).json({ result: 'ok' });
1014-
next();
10151012
} catch (error) {
10161013
logger.error(error, 'error processing core-node /new_mempool_tx');
10171014
res.status(500).json({ error: error });
10181015
}
1019-
}),
1020-
handleRawEventRequest
1016+
})
10211017
);
10221018

10231019
app.post(
10241020
'/drop_mempool_tx',
1025-
asyncHandler(async (req, res, next) => {
1021+
asyncHandler(async (req, res) => {
10261022
try {
10271023
const msg: CoreNodeDropMempoolTxMessage = req.body;
10281024
await messageHandler.handleDroppedMempoolTxs(msg, db);
1025+
await handleRawEventRequest(req);
10291026
res.status(200).json({ result: 'ok' });
1030-
next();
10311027
} catch (error) {
10321028
logger.error(error, 'error processing core-node /drop_mempool_tx');
10331029
res.status(500).json({ error: error });
10341030
}
1035-
}),
1036-
handleRawEventRequest
1031+
})
10371032
);
10381033

10391034
app.post(
10401035
'/attachments/new',
1041-
asyncHandler(async (req, res, next) => {
1036+
asyncHandler(async (req, res) => {
10421037
try {
10431038
const msg: CoreNodeAttachmentMessage[] = req.body;
10441039
await messageHandler.handleNewAttachment(msg, db);
1040+
await handleRawEventRequest(req);
10451041
res.status(200).json({ result: 'ok' });
1046-
next();
10471042
} catch (error) {
10481043
logger.error(error, 'error processing core-node /attachments/new');
10491044
res.status(500).json({ error: error });
10501045
}
1051-
}),
1052-
handleRawEventRequest
1046+
})
10531047
);
10541048

10551049
app.post(
10561050
'/new_microblocks',
1057-
asyncHandler(async (req, res, next) => {
1051+
asyncHandler(async (req, res) => {
10581052
try {
10591053
const msg: CoreNodeMicroblockMessage = req.body;
10601054
await messageHandler.handleMicroblockMessage(opts.chainId, msg, db);
1055+
await handleRawEventRequest(req);
10611056
res.status(200).json({ result: 'ok' });
1062-
next();
10631057
} catch (error) {
10641058
logger.error(error, 'error processing core-node /new_microblocks');
10651059
res.status(500).json({ error: error });
10661060
}
1067-
}),
1068-
handleRawEventRequest
1061+
})
10691062
);
10701063

10711064
app.post('*', (req, res, next) => {

src/tests-event-replay/raw-event-request-tests.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,69 @@ describe('Events table', () => {
8181
}
8282
);
8383
});
84+
85+
test('Large event requests are stored correctly', async () => {
86+
const getRawEventCount = async () => {
87+
const [row] = await client<{ count: string }[]>`SELECT count(*) from event_observer_requests`;
88+
return Number(row.count);
89+
};
90+
91+
await useWithCleanup(
92+
async () => {
93+
const eventServer = await startEventServer({
94+
datastore: db,
95+
chainId: ChainID.Mainnet,
96+
serverHost: '127.0.0.1',
97+
serverPort: 0,
98+
});
99+
return [eventServer, eventServer.closeAsync] as const;
100+
},
101+
async eventServer => {
102+
// split the tsv file into lines, split each line by tab, find the first line that has a cell value of `/new_block`
103+
const sampleTsv = fs
104+
.readFileSync('src/tests-event-replay/tsv/mainnet-block0.tsv', 'utf8')
105+
.split('\n')
106+
.map(line => line.split('\t'))
107+
.find(line => line[2] === '/new_block');
108+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
109+
const sampleNewBlock = JSON.parse(sampleTsv![3]);
110+
console.log(sampleTsv);
111+
// Create a huge JSON object, 10000 nodes, 20 layers deep, some nodes containing 4 megabytes of data
112+
function generateNestedObject(depth: number, nodesPerLevel: number, currentDepth = 1): any {
113+
if (currentDepth > depth) {
114+
// Return a leaf object instead of trying to link back to the top-level node
115+
return { info: `Leaf at depth ${currentDepth}` };
116+
}
117+
// Create a new object for each call to ensure uniqueness
118+
const currentNode: any = {};
119+
for (let i = 0; i < nodesPerLevel; i++) {
120+
currentNode[`node_${currentDepth}_${i}`] =
121+
currentDepth === depth
122+
? { info: `Simulated large node leaf at ${currentDepth}_${i}` }
123+
: generateNestedObject(depth, nodesPerLevel, currentDepth + 1);
124+
}
125+
return currentNode;
126+
}
127+
let hugeJsonObject = generateNestedObject(10, 3);
128+
hugeJsonObject = Object.assign(hugeJsonObject, sampleNewBlock);
129+
hugeJsonObject['very_large_value'] = 'x'.repeat(100 * 1024 * 1024); // 100 megabytes
130+
const rawEvent = {
131+
event_path: '/new_block',
132+
payload: JSON.stringify(hugeJsonObject),
133+
};
134+
const rawEventRequestCountBefore = await getRawEventCount();
135+
const response = await httpPostRequest({
136+
host: '127.0.0.1',
137+
port: eventServer.serverAddress.port,
138+
path: rawEvent.event_path,
139+
headers: { 'Content-Type': 'application/json' },
140+
body: Buffer.from(rawEvent.payload, 'utf8'),
141+
throwOnNotOK: false,
142+
});
143+
expect(response.statusCode).toBe(200);
144+
const rawEventRequestCountAfter = await getRawEventCount();
145+
expect(rawEventRequestCountAfter).toEqual(rawEventRequestCountBefore + 1);
146+
}
147+
);
148+
});
84149
});

0 commit comments

Comments
 (0)