Skip to content

Commit 5dce3e7

Browse files
committed
Refactored WithdrawalQueue to be stateless
1 parent f9dd0d7 commit 5dce3e7

File tree

4 files changed

+116
-85
lines changed

4 files changed

+116
-85
lines changed

packages/sequencer/src/protocol/baselayer/BaseLayer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import {
55
} from "@proto-kit/common";
66

77
import { IncomingMessageAdapter } from "../../settlement/messages/IncomingMessageAdapter";
8-
import type { OutgoingMessageQueue } from "../../settlement/messages/WithdrawalQueue";
8+
import type { OutgoingMessageAdapter } from "../../settlement/messages/WithdrawalQueue";
99

1010
export interface BaseLayerDependencyRecord extends DependencyRecord {
1111
IncomingMessageAdapter: DependencyDeclaration<IncomingMessageAdapter>;
1212
// TODO Move that to Database?
13-
OutgoingMessageQueue: DependencyDeclaration<OutgoingMessageQueue>;
13+
OutgoingMessageQueue: DependencyDeclaration<OutgoingMessageAdapter>;
1414
}
1515

1616
export interface BaseLayer extends DependencyFactory {

packages/sequencer/src/protocol/baselayer/NoopBaseLayer.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { noop } from "@proto-kit/common";
2-
import { PublicKey } from "o1js";
2+
import { Field, PublicKey } from "o1js";
33
import { Withdrawal } from "@proto-kit/protocol";
44

55
import {
@@ -10,7 +10,7 @@ import { IncomingMessageAdapter } from "../../settlement/messages/IncomingMessag
1010
import { PendingTransaction } from "../../mempool/PendingTransaction";
1111
import {
1212
OutgoingMessage,
13-
OutgoingMessageQueue,
13+
OutgoingMessageAdapter,
1414
} from "../../settlement/messages/WithdrawalQueue";
1515

1616
import { BaseLayer, BaseLayerDependencyRecord } from "./BaseLayer";
@@ -35,16 +35,11 @@ class NoopIncomingMessageAdapter implements IncomingMessageAdapter {
3535
}
3636
}
3737

38-
class NoopOutgoingMessageQueue implements OutgoingMessageQueue {
39-
length(): number {
40-
return 0;
41-
}
42-
43-
peek(num: number): OutgoingMessage<Withdrawal>[] {
44-
return [];
45-
}
46-
47-
pop(num: number): OutgoingMessage<Withdrawal>[] {
38+
class NoopOutgoingMessageAdapter implements OutgoingMessageAdapter {
39+
async fetchWithdrawals(
40+
tokenId: Field,
41+
offset: number
42+
): Promise<OutgoingMessage<Withdrawal>[]> {
4843
return [];
4944
}
5045
}
@@ -65,7 +60,7 @@ export class NoopBaseLayer extends SequencerModule implements BaseLayer {
6560
useClass: NoopIncomingMessageAdapter,
6661
},
6762
OutgoingMessageQueue: {
68-
useClass: NoopOutgoingMessageQueue,
63+
useClass: NoopOutgoingMessageAdapter,
6964
},
7065
};
7166
}

packages/sequencer/src/settlement/BridgingModule.ts

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore";
4444
import { FeeStrategy } from "../protocol/baselayer/fees/FeeStrategy";
4545
import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer";
4646

47-
import type { OutgoingMessageQueue } from "./messages/WithdrawalQueue";
47+
import type { OutgoingMessageAdapter } from "./messages/WithdrawalQueue";
4848
import type { SettlementModule } from "./SettlementModule";
4949
import { SettlementUtils } from "./utils/SettlementUtils";
5050
import { MinaTransactionSender } from "./transactions/MinaTransactionSender";
@@ -73,7 +73,7 @@ export class BridgingModule extends SequencerModule {
7373
@inject("SettlementModule")
7474
private readonly settlementModule: SettlementModule,
7575
@inject("OutgoingMessageQueue")
76-
private readonly outgoingMessageQueue: OutgoingMessageQueue,
76+
private readonly outgoingMessageQueue: OutgoingMessageAdapter,
7777
@inject("AsyncMerkleStore")
7878
private readonly merkleTreeStore: AsyncMerkleTreeStore,
7979
@inject("FeeStrategy")
@@ -326,7 +326,6 @@ export class BridgingModule extends SequencerModule {
326326
tx: Transaction<false, true>;
327327
}[]
328328
> {
329-
const length = this.outgoingMessageQueue.length();
330329
const { feepayer } = this.settlementModule.config;
331330
let { nonce } = options;
332331

@@ -364,9 +363,27 @@ export class BridgingModule extends SequencerModule {
364363
this.getBridgingModuleConfig().withdrawalStatePath.split(".");
365364
const basePath = Path.fromProperty(withdrawalModule, withdrawalStateName);
366365

366+
// TODO Not sure if we should re-fetch the account state here
367+
const outgoingMessageCursor = parseInt(
368+
bridgeContract.outgoingMessageCursor.get().toString(),
369+
10
370+
);
371+
372+
const pendingWithdrawals = await this.outgoingMessageQueue.fetchWithdrawals(
373+
tokenId,
374+
outgoingMessageCursor
375+
);
376+
367377
// Create withdrawal batches and send them as L1 transactions
368-
for (let i = 0; i < length; i += OUTGOING_MESSAGE_BATCH_SIZE) {
369-
const batch = this.outgoingMessageQueue.peek(OUTGOING_MESSAGE_BATCH_SIZE);
378+
for (
379+
let i = 0;
380+
i < pendingWithdrawals.length;
381+
i += OUTGOING_MESSAGE_BATCH_SIZE
382+
) {
383+
const batch = pendingWithdrawals.slice(
384+
i,
385+
i + OUTGOING_MESSAGE_BATCH_SIZE
386+
);
370387

371388
const keys = batch.map((x) =>
372389
Path.fromKey(basePath, OutgoingMessageKey, {
@@ -427,8 +444,6 @@ export class BridgingModule extends SequencerModule {
427444
"included"
428445
);
429446

430-
this.outgoingMessageQueue.pop(OUTGOING_MESSAGE_BATCH_SIZE);
431-
432447
txs.push({
433448
tx: signedTx,
434449
});
Lines changed: 84 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
import { inject, injectable } from "tsyringe";
22
import { Withdrawal } from "@proto-kit/protocol";
33
import { Field, Struct } from "o1js";
4-
import { splitArray } from "@proto-kit/common";
54

65
import type { BlockTriggerBase } from "../../protocol/production/trigger/BlockTrigger";
76
import { SettlementModule } from "../SettlementModule";
87
import { SequencerModule } from "../../sequencer/builder/SequencerModule";
98
import { Sequencer } from "../../sequencer/executor/Sequencer";
109
import { Block } from "../../storage/model/Block";
1110
import { BridgingModule } from "../BridgingModule";
11+
import {
12+
BlockStorage,
13+
HistoricalBlockStorage,
14+
} from "../../storage/repositories/BlockStorage";
15+
import { SettlementStorage } from "../../storage/repositories/SettlementStorage";
16+
import { HistoricalBatchStorage } from "../../storage/repositories/BatchStorage";
1217

1318
export interface OutgoingMessage<Type> {
1419
index: number;
@@ -34,99 +39,115 @@ export class WithdrawalEvent extends Struct({
3439
* In the future, this interface should be flexibly typed so that the
3540
* outgoing message type is not limited to Withdrawals
3641
*/
37-
export interface OutgoingMessageQueue {
38-
peek: (num: number) => OutgoingMessage<Withdrawal>[];
39-
pop: (num: number) => OutgoingMessage<Withdrawal>[];
40-
length: () => number;
42+
export interface OutgoingMessageAdapter {
43+
fetchWithdrawals(
44+
tokenId: Field,
45+
offset: number
46+
): Promise<OutgoingMessage<Withdrawal>[]>;
4147
}
4248

4349
@injectable()
4450
export class WithdrawalQueue
4551
extends SequencerModule
46-
implements OutgoingMessageQueue
52+
implements OutgoingMessageAdapter
4753
{
48-
private lockedQueue: Block[] = [];
49-
50-
private unlockedQueue: OutgoingMessage<Withdrawal>[] = [];
51-
5254
private outgoingWithdrawalEvents: string[] = [];
5355

5456
public constructor(
5557
@inject("Sequencer")
5658
private readonly sequencer: Sequencer<{
5759
BlockTrigger: typeof BlockTriggerBase;
5860
SettlementModule: typeof SettlementModule;
59-
}>
61+
}>,
62+
@inject("BlockStorage")
63+
private readonly blockStorage: BlockStorage & HistoricalBlockStorage,
64+
@inject("BatchStorage")
65+
private readonly batchStorage: HistoricalBatchStorage,
66+
@inject("SettlementStorage")
67+
private readonly settlementStorage: SettlementStorage
6068
) {
6169
super();
6270
}
6371

64-
public peek(num: number): OutgoingMessage<Withdrawal>[] {
65-
return this.unlockedQueue.slice(0, num);
72+
private extractEventsFromBlock(block: Block) {
73+
return block.transactions.flatMap((result) =>
74+
result.events
75+
.filter((event) =>
76+
this.outgoingWithdrawalEvents.includes(event.eventName)
77+
)
78+
.map((event) => WithdrawalEvent.fromFields(event.data))
79+
);
6680
}
6781

68-
public pop(num: number): OutgoingMessage<Withdrawal>[] {
69-
const slice = this.peek(num);
70-
this.unlockedQueue = this.unlockedQueue.slice(num);
71-
return slice;
82+
private async getLatestSettledBlock(): Promise<Block | undefined> {
83+
const settlement = await this.settlementStorage.getLatestSettlement();
84+
if (settlement !== undefined) {
85+
const lastBatch = settlement.batches.at(-1);
86+
if (lastBatch !== undefined) {
87+
const batch = await this.batchStorage.getBatchAt(lastBatch);
88+
if (batch !== undefined) {
89+
const blockHash = batch.blockHashes.at(-1);
90+
if (blockHash !== undefined) {
91+
return await this.blockStorage.getBlock(blockHash);
92+
}
93+
}
94+
}
95+
}
96+
return undefined;
7297
}
7398

74-
public length() {
75-
return this.unlockedQueue.length;
99+
private async findBlockWithEvent(tokenId: Field, index: number) {
100+
let block = await this.getLatestSettledBlock();
101+
102+
// Casting to defined here is fine, bcs in all cases where that could be undefined,
103+
// we break and return undefined all together.
104+
const blockHistory = [block!];
105+
106+
while (block !== undefined) {
107+
const events = this.extractEventsFromBlock(block);
108+
const found = events.find((withdrawalEvent) => {
109+
return withdrawalEvent.key.tokenId
110+
.equals(tokenId)
111+
.and(withdrawalEvent.key.index.equals(index))
112+
.toBoolean();
113+
});
114+
if (found !== undefined) {
115+
return blockHistory.reverse();
116+
}
117+
if (block.previousBlockHash !== undefined) {
118+
// eslint-disable-next-line no-await-in-loop
119+
block = await this.blockStorage.getBlock(
120+
block.previousBlockHash.toString()
121+
);
122+
blockHistory.push(block!);
123+
}
124+
}
125+
return undefined;
126+
}
127+
128+
public async fetchWithdrawals(
129+
tokenId: Field,
130+
offset: number
131+
): Promise<OutgoingMessage<Withdrawal>[]> {
132+
const blocks = await this.findBlockWithEvent(tokenId, offset);
133+
134+
const events = blocks
135+
?.flatMap((block) => this.extractEventsFromBlock(block))
136+
?.map((event) => ({
137+
index: parseInt(event.key.index.toString(), 10),
138+
value: event.value,
139+
}));
140+
return events ?? [];
76141
}
77142

78143
public async start(): Promise<void> {
79144
// Hacky workaround for this cyclic dependency
80-
const settlementModule = this.sequencer.resolveOrFail(
81-
"SettlementModule",
82-
SettlementModule
83-
);
84145
const bridgingModule = this.sequencer.resolveOrFail(
85146
"BridgingModule",
86147
BridgingModule
87148
);
88149

89150
const { withdrawalEventName } = bridgingModule.getBridgingModuleConfig();
90151
this.outgoingWithdrawalEvents = [withdrawalEventName];
91-
92-
this.sequencer.events.on("block-produced", (block) => {
93-
this.lockedQueue.push(block);
94-
});
95-
96-
// TODO Add event settlement-included and register it there
97-
settlementModule.events.on("settlement-submitted", (batch) => {
98-
// This finds out which blocks are contained in this batch and extracts only from those
99-
const { inBatch, notInBatch } = splitArray(this.lockedQueue, (block) =>
100-
batch.blockHashes.includes(block.hash.toString())
101-
? "inBatch"
102-
: "notInBatch"
103-
);
104-
105-
const withdrawals = (inBatch ?? []).flatMap((block) => {
106-
return block.transactions
107-
.flatMap((tx) =>
108-
tx.events
109-
.filter(
110-
(event) => event.eventName === this.outgoingWithdrawalEvents[0]
111-
)
112-
.map((event) => {
113-
return {
114-
tx,
115-
event,
116-
};
117-
})
118-
)
119-
.map<OutgoingMessage<Withdrawal>>(({ tx, event }) => {
120-
const withdrawalEvent = WithdrawalEvent.fromFields(event.data);
121-
122-
return {
123-
index: Number(withdrawalEvent.key.index.toString()),
124-
value: withdrawalEvent.value,
125-
};
126-
});
127-
});
128-
this.unlockedQueue.push(...withdrawals);
129-
this.lockedQueue = notInBatch ?? [];
130-
});
131152
}
132153
}

0 commit comments

Comments
 (0)