Skip to content

Commit f14da2e

Browse files
authored
Merge pull request #274 from proto-kit/refactor/withdrawals
Refactored WithdrawalQueue to be stateless
2 parents 31ba89b + 76b88db commit f14da2e

File tree

4 files changed

+118
-85
lines changed

4 files changed

+118
-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
@@ -45,7 +45,7 @@ import { AsyncMerkleTreeStore } from "../state/async/AsyncMerkleTreeStore";
4545
import { FeeStrategy } from "../protocol/baselayer/fees/FeeStrategy";
4646
import type { MinaBaseLayer } from "../protocol/baselayer/MinaBaseLayer";
4747

48-
import type { OutgoingMessageQueue } from "./messages/WithdrawalQueue";
48+
import type { OutgoingMessageAdapter } from "./messages/WithdrawalQueue";
4949
import type { SettlementModule } from "./SettlementModule";
5050
import { SettlementUtils } from "./utils/SettlementUtils";
5151
import { MinaTransactionSender } from "./transactions/MinaTransactionSender";
@@ -74,7 +74,7 @@ export class BridgingModule extends SequencerModule {
7474
@inject("SettlementModule")
7575
private readonly settlementModule: SettlementModule,
7676
@inject("OutgoingMessageQueue")
77-
private readonly outgoingMessageQueue: OutgoingMessageQueue,
77+
private readonly outgoingMessageQueue: OutgoingMessageAdapter,
7878
@inject("AsyncMerkleStore")
7979
private readonly merkleTreeStore: AsyncMerkleTreeStore,
8080
@inject("FeeStrategy")
@@ -327,7 +327,6 @@ export class BridgingModule extends SequencerModule {
327327
tx: Transaction<false, true>;
328328
}[]
329329
> {
330-
const length = this.outgoingMessageQueue.length();
331330
const { feepayer } = this.settlementModule.config;
332331
let { nonce } = options;
333332

@@ -369,9 +368,27 @@ export class BridgingModule extends SequencerModule {
369368
PROTOKIT_PREFIXES.STATE_RUNTIME
370369
);
371370

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

376393
const keys = batch.map((x) =>
377394
Path.fromKey(basePath, OutgoingMessageKey, {
@@ -432,8 +449,6 @@ export class BridgingModule extends SequencerModule {
432449
"included"
433450
);
434451

435-
this.outgoingMessageQueue.pop(OUTGOING_MESSAGE_BATCH_SIZE);
436-
437452
txs.push({
438453
tx: signedTx,
439454
});
Lines changed: 86 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,117 @@ 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+
// TODO Not really efficient right now in regards to DB trips, can be
83+
// easily built as a join query though
84+
private async getLatestSettledBlock(): Promise<Block | undefined> {
85+
const settlement = await this.settlementStorage.getLatestSettlement();
86+
if (settlement !== undefined) {
87+
const lastBatch = settlement.batches.at(-1);
88+
if (lastBatch !== undefined) {
89+
const batch = await this.batchStorage.getBatchAt(lastBatch);
90+
if (batch !== undefined) {
91+
const blockHash = batch.blockHashes.at(-1);
92+
if (blockHash !== undefined) {
93+
return await this.blockStorage.getBlock(blockHash);
94+
}
95+
}
96+
}
97+
}
98+
return undefined;
7299
}
73100

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

78145
public async start(): Promise<void> {
79146
// Hacky workaround for this cyclic dependency
80-
const settlementModule = this.sequencer.resolveOrFail(
81-
"SettlementModule",
82-
SettlementModule
83-
);
84147
const bridgingModule = this.sequencer.resolveOrFail(
85148
"BridgingModule",
86149
BridgingModule
87150
);
88151

89152
const { withdrawalEventName } = bridgingModule.getBridgingModuleConfig();
90153
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-
});
131154
}
132155
}

0 commit comments

Comments
 (0)