-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtransitions.ts
More file actions
340 lines (326 loc) · 11.7 KB
/
Copy pathtransitions.ts
File metadata and controls
340 lines (326 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
/**
* Attester state transition logic
*
* This module handles all state transitions for attesters based PURELY on on-chain state:
* - Provider queue membership (from staking contract)
* - Rollup entry queue membership (from rollup contract)
* - On-chain attester status (NONE, VALIDATING, ZOMBIE, EXITING)
*
* Coinbase configuration is tracked separately and does NOT affect state transitions.
*/
import {
AttesterState,
updateAttesterState,
getStakingProviderData,
getAttesterState,
} from "./index.js";
import { AttesterOnChainStatus } from "../../types/index.js";
/**
* Check if an attester is in the provider's queue
* This is derived from the staking provider metrics/state
*
* @param network - The network name
* @param attesterAddress - The attester's Ethereum address
* @returns true if the attester is in the provider's queue
*/
export function isAttesterInProviderQueue(
network: string,
attesterAddress: string,
): boolean {
const stakingProviderData = getStakingProviderData(network);
if (!stakingProviderData) {
return false;
}
// Check if attester is in the queue array
return stakingProviderData.queue.some(
(addr) => addr.toLowerCase() === attesterAddress.toLowerCase(),
);
}
/**
* Handle state transitions for an attester based on on-chain state ONLY
*
* State machine (purely on-chain):
* NEW → IN_STAKING_PROVIDER_QUEUE (when added to provider queue)
* IN_STAKING_PROVIDER_QUEUE → ROLLUP_ENTRY_QUEUE (when moved to rollup entry queue)
* ROLLUP_ENTRY_QUEUE → ACTIVE (when status becomes VALIDATING)
* ROLLUP_ENTRY_QUEUE → NO_LONGER_ACTIVE (when status is NONE with zero balance, i.e. removed before activation)
* ACTIVE → NO_LONGER_ACTIVE (when status becomes ZOMBIE, EXITING, or NONE)
* NO_LONGER_ACTIVE → ACTIVE (when status recovers to VALIDATING on-chain)
*
* @param network - The network name
* @param attesterAddress - The attester's Ethereum address
* @param currentState - Current state of the attester
*/
export async function handleStateTransitions(
network: string,
attesterAddress: string,
currentState: AttesterState,
): Promise<void> {
const attesterState = getAttesterState(network, attesterAddress);
const onChainView = attesterState?.onChainView;
const isInProviderQueue = isAttesterInProviderQueue(network, attesterAddress);
switch (currentState) {
case AttesterState.NEW:
// Check if attester joined the provider queue
if (isInProviderQueue) {
console.log(
`[${network}] Attester ${attesterAddress} joined provider queue (NEW → IN_STAKING_PROVIDER_QUEUE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.IN_STAKING_PROVIDER_QUEUE,
);
}
// Check if attester went directly to rollup entry queue (has onChainView with balance but not VALIDATING)
else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE &&
onChainView.effectiveBalance > 0n
) {
console.log(
`[${network}] Attester ${attesterAddress} entered rollup entry queue directly (NEW → ROLLUP_ENTRY_QUEUE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.ROLLUP_ENTRY_QUEUE,
);
}
// Check if attester was registered but already exited (status NONE with zero balance)
else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE &&
onChainView.effectiveBalance === 0n
) {
console.log(
`[${network}] Attester ${attesterAddress} was registered but already exited (NEW → NO_LONGER_ACTIVE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.NO_LONGER_ACTIVE,
);
}
// Check if attester became active directly
else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.VALIDATING
) {
console.log(
`[${network}] Attester ${attesterAddress} became active directly (NEW → ACTIVE)`,
);
updateAttesterState(network, attesterAddress, AttesterState.ACTIVE);
}
break;
case AttesterState.IN_STAKING_PROVIDER_QUEUE:
// Check if attester left provider queue
if (!isInProviderQueue) {
// Check if they're now in rollup entry queue or became active
if (
onChainView &&
onChainView.status === AttesterOnChainStatus.VALIDATING
) {
console.log(
`[${network}] Attester ${attesterAddress} left provider queue and became active (IN_STAKING_PROVIDER_QUEUE → ACTIVE)`,
);
updateAttesterState(network, attesterAddress, AttesterState.ACTIVE);
} else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE &&
onChainView.effectiveBalance > 0n
) {
console.log(
`[${network}] Attester ${attesterAddress} left provider queue, now in rollup entry queue (IN_STAKING_PROVIDER_QUEUE → ROLLUP_ENTRY_QUEUE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.ROLLUP_ENTRY_QUEUE,
);
} else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE &&
onChainView.effectiveBalance === 0n
) {
console.log(
`[${network}] Attester ${attesterAddress} left provider queue and was removed (zero balance) (IN_STAKING_PROVIDER_QUEUE → NO_LONGER_ACTIVE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.NO_LONGER_ACTIVE,
);
} else {
// No on-chain view but not in provider queue anymore
// This could mean they left the queue without being registered yet
// Wait for rollup scraper to update onChainView
console.log(
`[${network}] Attester ${attesterAddress} left provider queue, waiting for on-chain view update`,
);
}
}
break;
case AttesterState.ROLLUP_ENTRY_QUEUE:
// Check if attester became active
if (
onChainView &&
onChainView.status === AttesterOnChainStatus.VALIDATING
) {
console.log(
`[${network}] Attester ${attesterAddress} became active (ROLLUP_ENTRY_QUEUE → ACTIVE)`,
);
updateAttesterState(network, attesterAddress, AttesterState.ACTIVE);
}
// Check if attester was removed before activation (status NONE with zero balance)
else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE &&
onChainView.effectiveBalance === 0n
) {
console.log(
`[${network}] Attester ${attesterAddress} removed from entry queue (zero balance) (ROLLUP_ENTRY_QUEUE → NO_LONGER_ACTIVE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.NO_LONGER_ACTIVE,
);
}
// Check for other unexpected states (ZOMBIE, EXITING)
else if (
onChainView &&
(onChainView.status === AttesterOnChainStatus.ZOMBIE ||
onChainView.status === AttesterOnChainStatus.EXITING)
) {
console.log(
`[${network}] Attester ${attesterAddress} exited from entry queue (status: ${AttesterOnChainStatus[onChainView.status]}) (ROLLUP_ENTRY_QUEUE → NO_LONGER_ACTIVE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.NO_LONGER_ACTIVE,
);
}
// No on-chain view at all (shouldn't happen)
else if (!onChainView) {
console.warn(
`[${network}] Attester ${attesterAddress} in ROLLUP_ENTRY_QUEUE has no on-chain view`,
);
}
break;
case AttesterState.ACTIVE:
// Check if attester is no longer active (ZOMBIE or EXITING)
if (
onChainView &&
(onChainView.status === AttesterOnChainStatus.ZOMBIE ||
onChainView.status === AttesterOnChainStatus.EXITING)
) {
console.log(
`[${network}] Attester ${attesterAddress} is no longer active (status: ${AttesterOnChainStatus[onChainView.status]}) (ACTIVE → NO_LONGER_ACTIVE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.NO_LONGER_ACTIVE,
);
}
// Check if attester fully exited (status reverted to NONE)
else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE
) {
console.log(
`[${network}] Attester ${attesterAddress} fully exited (status: NONE) (ACTIVE → NO_LONGER_ACTIVE)`,
);
updateAttesterState(
network,
attesterAddress,
AttesterState.NO_LONGER_ACTIVE,
);
}
// No on-chain view at all
else if (!onChainView) {
console.error(
`[${network}] CRITICAL: Active attester ${attesterAddress} has no on-chain view!`,
);
}
break;
case AttesterState.NO_LONGER_ACTIVE:
// Recovery: if attester is back to VALIDATING on-chain, transition back to ACTIVE
if (
onChainView &&
onChainView.status === AttesterOnChainStatus.VALIDATING
) {
console.log(
`[${network}] Attester ${attesterAddress} recovered to VALIDATING on-chain (NO_LONGER_ACTIVE → ACTIVE)`,
);
updateAttesterState(network, attesterAddress, AttesterState.ACTIVE);
}
break;
default:
console.warn(
`[${network}] Unknown state ${currentState} for attester ${attesterAddress}`,
);
}
}
/**
* Process a single attester and handle its state transitions
* State transitions are based PURELY on on-chain data.
*
* @param network - The network name
* @param attesterAddress - The attester's Ethereum address
* @param currentState - Current state entry (or undefined if new)
*/
export async function processAttesterState(
network: string,
attesterAddress: string,
currentState: AttesterState | undefined,
): Promise<void> {
const attesterState = getAttesterState(network, attesterAddress);
const onChainView = attesterState?.onChainView;
const isInProviderQueue = isAttesterInProviderQueue(network, attesterAddress);
// If no current state, initialize the attester based on on-chain state
if (!currentState) {
// Determine initial state from on-chain data
if (onChainView && onChainView.status === AttesterOnChainStatus.VALIDATING) {
// Already active on-chain
updateAttesterState(network, attesterAddress, AttesterState.ACTIVE);
} else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE &&
onChainView.effectiveBalance > 0n
) {
// In rollup entry queue (has on-chain view with balance but not yet validating)
updateAttesterState(
network,
attesterAddress,
AttesterState.ROLLUP_ENTRY_QUEUE,
);
} else if (
onChainView &&
onChainView.status === AttesterOnChainStatus.NONE &&
onChainView.effectiveBalance === 0n
) {
// Was registered on-chain but has zero balance - already exited
updateAttesterState(
network,
attesterAddress,
AttesterState.NO_LONGER_ACTIVE,
);
} else if (isInProviderQueue) {
// In provider queue
updateAttesterState(
network,
attesterAddress,
AttesterState.IN_STAKING_PROVIDER_QUEUE,
);
} else {
// Not in any queue or on-chain
updateAttesterState(network, attesterAddress, AttesterState.NEW);
}
return;
}
// Handle transitions for existing attesters
await handleStateTransitions(network, attesterAddress, currentState);
}