Skip to content

Commit dfab89c

Browse files
committed
Added unit test for result generation recovery
1 parent 72d7f2b commit dfab89c

File tree

2 files changed

+134
-1
lines changed

2 files changed

+134
-1
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
231231
return block;
232232
}
233233

234-
public async start() {
234+
public async blockResultCompleteCheck() {
235235
// Check if metadata height is behind block production.
236236
// This can happen when the sequencer crashes after a block has been produced
237237
// but before the metadata generation has finished
@@ -245,4 +245,8 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
245245
}
246246
// If we reach here, its a genesis startup, no blocks exist yet
247247
}
248+
249+
public async start() {
250+
await this.blockResultCompleteCheck();
251+
}
248252
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import "reflect-metadata";
2+
import { Runtime } from "@proto-kit/module";
3+
import { Protocol } from "@proto-kit/protocol";
4+
import { VanillaProtocolModules } from "@proto-kit/library";
5+
import { AppChain } from "@proto-kit/sdk";
6+
import { container } from "tsyringe";
7+
import { jest } from "@jest/globals";
8+
import { expectDefined } from "@proto-kit/common";
9+
10+
import {
11+
BlockQueue,
12+
ManualBlockTrigger,
13+
TransactionExecutionService,
14+
} from "../../../../src";
15+
import { ProtocolStateTestHook } from "../../../integration/mocks/ProtocolStateTestHook";
16+
import {
17+
DefaultTestingSequencerModules,
18+
testingSequencerFromModules,
19+
} from "../../../TestingSequencer";
20+
import { Balance } from "../../../integration/mocks/Balance";
21+
22+
describe("atomic block production", () => {
23+
let appchain: AppChain<any, any, DefaultTestingSequencerModules, any>;
24+
25+
let trigger: ManualBlockTrigger;
26+
27+
beforeEach(async () => {
28+
const runtimeClass = Runtime.from({
29+
modules: {
30+
Balance,
31+
},
32+
33+
config: {
34+
Balance: {},
35+
},
36+
});
37+
38+
const sequencerClass = testingSequencerFromModules({});
39+
40+
const protocolClass = Protocol.from({
41+
modules: VanillaProtocolModules.mandatoryModules({
42+
ProtocolStateTestHook,
43+
}),
44+
});
45+
46+
const app = AppChain.from({
47+
Runtime: runtimeClass,
48+
Sequencer: sequencerClass,
49+
Protocol: protocolClass,
50+
modules: {},
51+
});
52+
53+
app.configure({
54+
Sequencer: {
55+
Database: {},
56+
BlockTrigger: {},
57+
Mempool: {},
58+
BatchProducerModule: {},
59+
BlockProducerModule: {},
60+
LocalTaskWorkerModule: {},
61+
BaseLayer: {},
62+
TaskQueue: {},
63+
FeeStrategy: {},
64+
ProtocolStartupModule: {},
65+
},
66+
Runtime: {
67+
Balance: {},
68+
},
69+
Protocol: {
70+
AccountState: {},
71+
BlockProver: {},
72+
StateTransitionProver: {},
73+
BlockHeight: {},
74+
LastStateRoot: {},
75+
ProtocolStateTestHook: {},
76+
},
77+
});
78+
79+
appchain = app;
80+
81+
// Start AppChain
82+
await app.start(container.createChildContainer());
83+
84+
trigger = app.sequencer.resolve("BlockTrigger");
85+
});
86+
87+
/**
88+
* This test does two passes on block generation.
89+
* In the first, the metadata generation function is mocked to throw an error
90+
* This leads to the block being produced, but the result generation to fail.
91+
* Then, the mock is released. After that, the blockResultCompleteCheck()
92+
* should correctly detect the missing results and re-generate it, so that
93+
* the second block production can succeed
94+
*/
95+
it("should recover from non-generated metadata", async () => {
96+
expect.assertions(6);
97+
98+
const module = appchain.sequencer.dependencyContainer.resolve(
99+
TransactionExecutionService
100+
);
101+
102+
module.generateMetadataForNextBlock = jest
103+
.fn(module.generateMetadataForNextBlock)
104+
.mockImplementationOnce(() => {
105+
throw new Error("Test error");
106+
});
107+
108+
await expect(() => trigger.produceBlock()).rejects.toThrow();
109+
110+
// This checks that it correctly throws when producing a block with no previous result existing
111+
await expect(() => trigger.produceBlock()).rejects.toThrow();
112+
113+
await appchain.sequencer
114+
.resolve("BlockProducerModule")
115+
.blockResultCompleteCheck();
116+
117+
const blockQueue =
118+
appchain.sequencer.dependencyContainer.resolve<BlockQueue>("BlockQueue");
119+
const queueData = await blockQueue.getLatestBlockAndResult();
120+
121+
expectDefined(queueData);
122+
expectDefined(queueData.result);
123+
124+
const block = await trigger.produceBlock();
125+
126+
expectDefined(block);
127+
expect(block.height.toString()).toBe("1");
128+
});
129+
});

0 commit comments

Comments
 (0)