1
1
import { inject , injectable } from "tsyringe" ;
2
2
import { Withdrawal } from "@proto-kit/protocol" ;
3
3
import { Field , Struct } from "o1js" ;
4
- import { splitArray } from "@proto-kit/common" ;
5
4
6
5
import type { BlockTriggerBase } from "../../protocol/production/trigger/BlockTrigger" ;
7
6
import { SettlementModule } from "../SettlementModule" ;
8
7
import { SequencerModule } from "../../sequencer/builder/SequencerModule" ;
9
8
import { Sequencer } from "../../sequencer/executor/Sequencer" ;
10
9
import { Block } from "../../storage/model/Block" ;
11
10
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" ;
12
17
13
18
export interface OutgoingMessage < Type > {
14
19
index : number ;
@@ -34,99 +39,115 @@ export class WithdrawalEvent extends Struct({
34
39
* In the future, this interface should be flexibly typed so that the
35
40
* outgoing message type is not limited to Withdrawals
36
41
*/
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 > [ ] > ;
41
47
}
42
48
43
49
@injectable ( )
44
50
export class WithdrawalQueue
45
51
extends SequencerModule
46
- implements OutgoingMessageQueue
52
+ implements OutgoingMessageAdapter
47
53
{
48
- private lockedQueue : Block [ ] = [ ] ;
49
-
50
- private unlockedQueue : OutgoingMessage < Withdrawal > [ ] = [ ] ;
51
-
52
54
private outgoingWithdrawalEvents : string [ ] = [ ] ;
53
55
54
56
public constructor (
55
57
@inject ( "Sequencer" )
56
58
private readonly sequencer : Sequencer < {
57
59
BlockTrigger : typeof BlockTriggerBase ;
58
60
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
60
68
) {
61
69
super ( ) ;
62
70
}
63
71
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
+ ) ;
66
80
}
67
81
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 ;
72
97
}
73
98
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 ?? [ ] ;
76
141
}
77
142
78
143
public async start ( ) : Promise < void > {
79
144
// Hacky workaround for this cyclic dependency
80
- const settlementModule = this . sequencer . resolveOrFail (
81
- "SettlementModule" ,
82
- SettlementModule
83
- ) ;
84
145
const bridgingModule = this . sequencer . resolveOrFail (
85
146
"BridgingModule" ,
86
147
BridgingModule
87
148
) ;
88
149
89
150
const { withdrawalEventName } = bridgingModule . getBridgingModuleConfig ( ) ;
90
151
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
- } ) ;
131
152
}
132
153
}
0 commit comments