Skip to content

Commit 8b7f183

Browse files
authored
Merge pull request #325 from proto-kit/fix/tx-hook-fail-messages
Remove tx hook fail for messages
2 parents 5fff36d + 0419fcc commit 8b7f183

File tree

6 files changed

+96
-41
lines changed

6 files changed

+96
-41
lines changed

packages/protocol/src/prover/block/BlockProver.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,8 @@ export class BlockProverProgrammable extends ZkProgrammable<
171171
// Apply beforeTransaction hook state transitions
172172
const beforeBatch = await this.executeTransactionHooks(
173173
async (module, args) => await module.beforeTransaction(args),
174-
beforeTxHookArguments
174+
beforeTxHookArguments,
175+
isMessage
175176
);
176177

177178
state = this.addTransactionToBundle(
@@ -200,7 +201,8 @@ export class BlockProverProgrammable extends ZkProgrammable<
200201

201202
const afterBatch = await this.executeTransactionHooks(
202203
async (module, args) => await module.afterTransaction(args),
203-
afterTxHookArguments
204+
afterTxHookArguments,
205+
isMessage
204206
);
205207
state.pendingSTBatches.push(afterBatch);
206208

@@ -266,20 +268,31 @@ export class BlockProverProgrammable extends ZkProgrammable<
266268
T extends BeforeTransactionHookArguments | AfterTransactionHookArguments,
267269
>(
268270
hook: (module: ProvableTransactionHook<unknown>, args: T) => Promise<void>,
269-
hookArguments: T
271+
hookArguments: T,
272+
isMessage: Bool
270273
) {
271-
const { batch } = await this.executeHooks(hookArguments, async () => {
272-
for (const module of this.transactionHooks) {
273-
// eslint-disable-next-line no-await-in-loop
274-
await hook(module, hookArguments);
275-
}
276-
});
274+
const { batch, rawStatus } = await this.executeHooks(
275+
hookArguments,
276+
async () => {
277+
for (const module of this.transactionHooks) {
278+
// eslint-disable-next-line no-await-in-loop
279+
await hook(module, hookArguments);
280+
}
281+
},
282+
isMessage
283+
);
284+
285+
// This is going to set applied to false in case the hook fails
286+
// (that's only possible for messages though as others are hard-asserted)
287+
batch.applied = rawStatus;
288+
277289
return batch;
278290
}
279291

280292
private async executeHooks<T>(
281293
contextArguments: RuntimeMethodExecutionData,
282-
method: () => Promise<T>
294+
method: () => Promise<T>,
295+
isMessage: Bool | undefined = undefined
283296
) {
284297
const executionContext = container.resolve(RuntimeMethodExecutionContext);
285298
executionContext.clear();
@@ -297,11 +310,23 @@ export class BlockProverProgrammable extends ZkProgrammable<
297310
const { stateTransitions, status, statusMessage } =
298311
executionContext.current().result;
299312

300-
status.assertTrue(`Transaction hook call failed: ${statusMessage ?? "-"}`);
313+
// See https://github.com/proto-kit/framework/issues/321 for why we do this here
314+
if (isMessage !== undefined) {
315+
// isMessage is defined for all tx hooks
316+
status
317+
.or(isMessage)
318+
.assertTrue(
319+
`Transaction hook call failed for non-message tx: ${statusMessage ?? "-"}`
320+
);
321+
} else {
322+
// isMessage is undefined for all block hooks
323+
status.assertTrue(`Block hook call failed: ${statusMessage ?? "-"}`);
324+
}
301325

302326
return {
303327
batch: this.constructBatch(stateTransitions, Bool(true)),
304328
result,
329+
rawStatus: status,
305330
};
306331
}
307332

packages/protocol/src/state/assert/assert.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function assert(condition: Bool, message?: string | (() => string)) {
1919
const status = condition.and(previousStatus);
2020

2121
Provable.asProver(() => {
22-
if (!condition.toBoolean()) {
22+
if (!condition.toBoolean() && previousStatus.toBoolean()) {
2323
const messageString =
2424
message !== undefined && typeof message === "function"
2525
? message()

packages/sdk/test/fees-failures.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ describe("fee errors due to limited funds in sender accounts", () => {
6060
appChain.setSigner(senderKey);
6161
});
6262

63+
afterAll(async () => {
64+
await appChain.close();
65+
});
66+
6367
it("should allow a free faucet transaction", async () => {
6468
expect.assertions(2);
6569

@@ -105,8 +109,7 @@ describe("fee errors due to limited funds in sender accounts", () => {
105109
await appChain.produceBlock();
106110

107111
expect(logSpy).toHaveBeenCalledWith(
108-
"Error in inclusion of tx, skipping",
109-
Error("Protocol hooks not executable: From balance is insufficient")
112+
"Error in inclusion of tx, skipping: Protocol hooks not executable: From balance is insufficient"
110113
);
111114

112115
const balance = await appChain.query.runtime.Balances.balances.get(

packages/sequencer/src/protocol/production/sequencing/BlockProductionService.ts

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ import {
1515
} from "@proto-kit/protocol";
1616
import { Field } from "o1js";
1717
import { log } from "@proto-kit/common";
18+
import { match } from "ts-pattern";
1819

19-
import { Block, BlockWithResult } from "../../../storage/model/Block";
20+
import {
21+
Block,
22+
BlockWithResult,
23+
TransactionExecutionResult,
24+
} from "../../../storage/model/Block";
2025
import { CachedStateService } from "../../../state/state/CachedStateService";
2126
import { PendingTransaction } from "../../../mempool/PendingTransaction";
2227
import { AsyncStateService } from "../../../state/async/AsyncStateService";
@@ -27,9 +32,16 @@ import { trace } from "../../../logging/trace";
2732
import {
2833
BlockTrackers,
2934
executeWithExecutionContext,
35+
TransactionExecutionResultStatus,
3036
TransactionExecutionService,
3137
} from "./TransactionExecutionService";
3238

39+
function isIncludedTxs(
40+
x: TransactionExecutionResultStatus
41+
): x is { status: "included"; result: TransactionExecutionResult } {
42+
return x.status === "included";
43+
}
44+
3345
@injectable()
3446
@scoped(Lifecycle.ContainerScoped)
3547
export class BlockProductionService {
@@ -146,8 +158,12 @@ export class BlockProductionService {
146158
return undefined;
147159
}
148160

161+
const includedTransactions = executionResults
162+
.filter(isIncludedTxs)
163+
.map((x) => x.result);
164+
149165
const block: Omit<Block, "hash"> = {
150-
transactions: executionResults.map((x) => x.result),
166+
transactions: includedTransactions,
151167
transactionsHash: newBlockState.transactionList.commitment,
152168
fromEternalTransactionsHash: lastBlock.toEternalTransactionsHash,
153169
toEternalTransactionsHash:
@@ -169,10 +185,17 @@ export class BlockProductionService {
169185

170186
const hash = Block.hash(block);
171187

172-
const includedTxs = executionResults.map((x) => ({
173-
hash: x.result.tx.hash().toString(),
174-
type: x.status,
175-
}));
188+
const includedTxs = executionResults.map((x) => {
189+
const txHash = match(x)
190+
.with({ status: "included" }, ({ result }) => result.tx)
191+
.otherwise(({ tx }) => tx)
192+
.hash()
193+
.toString();
194+
return {
195+
hash: txHash,
196+
type: x.status,
197+
};
198+
});
176199

177200
return {
178201
block: {

packages/sequencer/src/protocol/production/sequencing/TransactionExecutionService.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,14 @@ function traceLogSTs(msg: string, stateTransitions: StateTransition<any>[]) {
197197
);
198198
}
199199

200+
export type TransactionExecutionResultStatus =
201+
| {
202+
result: TransactionExecutionResult;
203+
status: "included";
204+
}
205+
| { tx: PendingTransaction; status: "skipped" }
206+
| { tx: PendingTransaction; status: "shouldRemove" };
207+
200208
@injectable()
201209
@scoped(Lifecycle.ContainerScoped)
202210
export class TransactionExecutionService {
@@ -276,15 +284,6 @@ export class TransactionExecutionService {
276284
runSimulated
277285
);
278286

279-
// if (!result.status.toBoolean()) {
280-
// const error = new Error(
281-
// `Protocol hooks not executable: ${result.statusMessage ?? "unknown"}`
282-
// );
283-
// log.debug("Protocol hook error stack trace:", result.stackTrace);
284-
// // Propagate stack trace from the assertion
285-
// throw error;
286-
// }
287-
288287
traceLogSTs(`${hookName} STs:`, result.stateTransitions);
289288

290289
return result;
@@ -328,23 +327,18 @@ export class TransactionExecutionService {
328327
);
329328
}
330329

330+
// eslint-disable-next-line sonarjs/cognitive-complexity
331331
public async createExecutionTraces(
332332
asyncStateService: CachedStateService,
333333
transactions: PendingTransaction[],
334334
networkState: NetworkState,
335335
state: BlockTrackers
336336
): Promise<{
337337
blockState: BlockTrackers;
338-
executionResults: {
339-
result: TransactionExecutionResult;
340-
status: "included" | "skipped" | "shouldRemove";
341-
}[];
338+
executionResults: TransactionExecutionResultStatus[];
342339
}> {
343340
let blockState = state;
344-
const executionResults: {
345-
result: TransactionExecutionResult;
346-
status: "included" | "skipped" | "shouldRemove";
347-
}[] = [];
341+
const executionResults: TransactionExecutionResultStatus[] = [];
348342

349343
const networkStateHash = networkState.hash();
350344

@@ -369,8 +363,14 @@ export class TransactionExecutionService {
369363
!executionTrace.hooksStatus.toBoolean() &&
370364
!executionTrace.tx.isMessage
371365
) {
366+
const actionMessage = shouldRemove
367+
? "removing as to removeWhen hooks"
368+
: "skipping";
369+
log.error(
370+
`Error in inclusion of tx, ${actionMessage}: Protocol hooks not executable: ${executionTrace.statusMessage ?? "unknown reason"}`
371+
);
372372
executionResults.push({
373-
result: executionTrace,
373+
tx,
374374
status: shouldRemove ? "shouldRemove" : "skipped",
375375
});
376376
} else {
@@ -381,7 +381,8 @@ export class TransactionExecutionService {
381381
}
382382
} catch (error) {
383383
if (error instanceof Error) {
384-
log.error("Error in inclusion of tx, skipping", error);
384+
log.error("Error in inclusion of tx, dropping", error);
385+
executionResults.push({ tx, status: "shouldRemove" });
385386
}
386387
}
387388
}
@@ -545,7 +546,10 @@ export class TransactionExecutionService {
545546
tx,
546547
hooksStatus: Bool(txHooksValid),
547548
status: runtimeResult.status,
548-
statusMessage: runtimeResult.statusMessage,
549+
statusMessage:
550+
beforeTxHookResult.statusMessage ??
551+
afterTxHookResult.statusMessage ??
552+
runtimeResult.statusMessage,
549553

550554
stateTransitions,
551555
events: beforeHookEvents.concat(runtimeResultEvents, afterHookEvents),

packages/sequencer/test/integration/MempoolTxRemoved.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe("block production", () => {
3333

3434
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
3535
mempool = sequencer.resolve("Mempool");
36-
});
36+
}, 60_000);
3737

3838
it("check tx is removed", async () => {
3939
await mempool.add(

0 commit comments

Comments
 (0)