Skip to content

Commit 42d26fd

Browse files
committed
Added recovery mechanism for missing block result
1 parent d2f7251 commit 42d26fd

File tree

5 files changed

+88
-16
lines changed

5 files changed

+88
-16
lines changed

packages/persistance/src/services/prisma/PrismaBlockStorage.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
BlockStorage,
99
BlockWithResult,
1010
BlockWithPreviousResult,
11+
BlockWithMaybeResult,
1112
} from "@proto-kit/sequencer";
1213
import { filterNonNull, log } from "@proto-kit/common";
1314
import {
@@ -39,7 +40,7 @@ export class PrismaBlockStorage
3940

4041
private async getBlockByQuery(
4142
where: { height: number } | { hash: string }
42-
): Promise<BlockWithResult | undefined> {
43+
): Promise<BlockWithMaybeResult | undefined> {
4344
const dbResult = await this.connection.prismaClient.block.findFirst({
4445
where,
4546
include: {
@@ -57,18 +58,15 @@ export class PrismaBlockStorage
5758
const transactions = dbResult.transactions.map<TransactionExecutionResult>(
5859
(txresult) => this.transactionResultMapper.mapIn([txresult, txresult.tx])
5960
);
60-
if (dbResult.result === undefined || dbResult.result === null) {
61-
throw new Error(
62-
`No Metadata has been set for block ${JSON.stringify(where)} yet`
63-
);
64-
}
6561

6662
return {
6763
block: {
6864
...this.blockMapper.mapIn(dbResult),
6965
transactions,
7066
},
71-
result: this.blockResultMapper.mapIn(dbResult.result),
67+
result: dbResult.result
68+
? this.blockResultMapper.mapIn(dbResult.result)
69+
: undefined,
7270
};
7371
}
7472

@@ -169,7 +167,9 @@ export class PrismaBlockStorage
169167
return (result?._max.height ?? -1) + 1;
170168
}
171169

172-
public async getLatestBlock(): Promise<BlockWithResult | undefined> {
170+
public async getLatestBlockAndResult(): Promise<
171+
BlockWithMaybeResult | undefined
172+
> {
173173
const latestBlock = await this.connection.prismaClient.$queryRaw<
174174
{ hash: string }[]
175175
>`SELECT b1."hash" FROM "Block" b1
@@ -185,6 +185,22 @@ export class PrismaBlockStorage
185185
});
186186
}
187187

188+
public async getLatestBlock(): Promise<BlockWithResult | undefined> {
189+
const result = await this.getLatestBlockAndResult();
190+
if (result !== undefined) {
191+
if (result.result === undefined) {
192+
throw new Error(
193+
`Block result for block ${result.block.height.toString()} not found`
194+
);
195+
}
196+
return {
197+
block: result.block,
198+
result: result.result,
199+
};
200+
}
201+
return result;
202+
}
203+
188204
public async getNewBlocks(): Promise<BlockWithPreviousResult[]> {
189205
const blocks = await this.connection.prismaClient.block.findMany({
190206
where: {

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,19 +158,31 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
158158
}> {
159159
const txs = await this.mempool.getTxs(this.maximumBlockSize());
160160

161-
const parentBlock = await this.blockQueue.getLatestBlock();
161+
const parentBlock = await this.blockQueue.getLatestBlockAndResult();
162+
163+
let metadata: BlockWithResult;
162164

163165
if (parentBlock === undefined) {
164166
log.debug(
165167
"No block metadata given, assuming first block, generating genesis metadata"
166168
);
169+
metadata = BlockWithResult.createEmpty();
170+
} else if (parentBlock.result === undefined) {
171+
throw new Error(
172+
`Metadata for block at height ${parentBlock.block.height.toString()} not available`
173+
);
174+
} else {
175+
metadata = {
176+
block: parentBlock.block,
177+
// By reconstructing this object, typescript correctly infers the result to be defined
178+
result: parentBlock.result,
179+
};
167180
}
168181

169182
const messages = await this.messageStorage.getMessages(
170183
parentBlock?.block.toMessagesHash.toString() ??
171184
ACTIONS_EMPTY_HASH.toString()
172185
);
173-
const metadata = parentBlock ?? BlockWithResult.createEmpty();
174186

175187
log.debug(
176188
`Block collected, ${txs.length} txs, ${messages.length} messages`
@@ -215,6 +227,17 @@ export class BlockProducerModule extends SequencerModule<BlockConfig> {
215227
}
216228

217229
public async start() {
218-
noop();
230+
// Check if metadata height is behind block production.
231+
// This can happen when the sequencer crashes after a block has been produced
232+
// but before the metadata generation has finished
233+
const latestBlock = await this.blockQueue.getLatestBlockAndResult();
234+
// eslint-disable-next-line sonarjs/no-collapsible-if
235+
if (latestBlock !== undefined) {
236+
if (latestBlock.result === undefined) {
237+
await this.generateMetadata(latestBlock.block);
238+
}
239+
// Here, the metadata has been computed already
240+
}
241+
// If we reach here, its a genesis startup, no blocks exist yet
219242
}
220243
}

packages/sequencer/src/storage/inmemory/InMemoryBlockStorage.ts

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,12 @@ import {
55
BlockQueue,
66
BlockStorage,
77
} from "../repositories/BlockStorage";
8-
import type { Block, BlockResult, BlockWithResult } from "../model/Block";
8+
import type {
9+
Block,
10+
BlockResult,
11+
BlockWithMaybeResult,
12+
BlockWithResult,
13+
} from "../model/Block";
914
import { BlockWithPreviousResult } from "../../protocol/production/BatchProducerModule";
1015
import { BatchStorage } from "../repositories/BatchStorage";
1116

@@ -29,10 +34,12 @@ export class InMemoryBlockStorage
2934
return this.blocks.length;
3035
}
3136

32-
public async getLatestBlock(): Promise<BlockWithResult | undefined> {
37+
public async getLatestBlockAndResult(): Promise<
38+
BlockWithMaybeResult | undefined
39+
> {
3340
const currentHeight = await this.getCurrentBlockHeight();
3441
const block = await this.getBlockAt(currentHeight - 1);
35-
const result = this.results[currentHeight - 1];
42+
const result: BlockResult | undefined = this.results[currentHeight - 1];
3643
if (block === undefined) {
3744
return undefined;
3845
}
@@ -42,6 +49,22 @@ export class InMemoryBlockStorage
4249
};
4350
}
4451

52+
public async getLatestBlock(): Promise<BlockWithResult | undefined> {
53+
const result = await this.getLatestBlockAndResult();
54+
if (result !== undefined) {
55+
if (result.result === undefined) {
56+
throw new Error(
57+
`Block result for block ${result.block.height.toString()} not found`
58+
);
59+
}
60+
return {
61+
block: result.block,
62+
result: result.result,
63+
};
64+
}
65+
return result;
66+
}
67+
4568
public async getNewBlocks(): Promise<BlockWithPreviousResult[]> {
4669
const latestBatch = await this.batchStorage.getLatestBatch();
4770

packages/sequencer/src/storage/model/Block.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ export interface BlockWithResult {
6161
result: BlockResult;
6262
}
6363

64+
export interface BlockWithMaybeResult {
65+
block: Block;
66+
result?: BlockResult;
67+
}
68+
6469
// eslint-disable-next-line @typescript-eslint/no-redeclare
6570
export const BlockWithResult = {
6671
createEmpty: () =>

packages/sequencer/src/storage/repositories/BlockStorage.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import { BlockWithPreviousResult } from "../../protocol/production/BatchProducerModule";
2-
import type { Block, BlockResult, BlockWithResult } from "../model/Block";
2+
import {
3+
Block,
4+
BlockResult,
5+
BlockWithMaybeResult,
6+
BlockWithResult,
7+
} from "../model/Block";
38

49
export interface BlockQueue {
510
pushBlock: (block: Block) => Promise<void>;
611
pushResult: (result: BlockResult) => Promise<void>;
712
getNewBlocks: () => Promise<BlockWithPreviousResult[]>;
8-
getLatestBlock: () => Promise<BlockWithResult | undefined>;
13+
getLatestBlockAndResult: () => Promise<BlockWithMaybeResult | undefined>;
914
}
1015

1116
export interface BlockStorage {

0 commit comments

Comments
 (0)