@aztec/sequencer-client 0.0.1-commit.9b94fc1 → 0.0.1-commit.9ee6fcc6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/sequencer-client.d.ts +24 -16
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +73 -32
- package/dest/config.d.ts +35 -9
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +106 -44
- package/dest/global_variable_builder/global_builder.d.ts +27 -14
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +65 -54
- package/dest/global_variable_builder/index.d.ts +2 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/index.d.ts +2 -3
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/config.d.ts +53 -20
- package/dest/publisher/config.d.ts.map +1 -1
- package/dest/publisher/config.js +124 -39
- package/dest/publisher/index.d.ts +2 -1
- package/dest/publisher/index.d.ts.map +1 -1
- package/dest/publisher/l1_tx_failed_store/factory.d.ts +11 -0
- package/dest/publisher/l1_tx_failed_store/factory.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/factory.js +22 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts +59 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/failed_tx_store.js +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts +15 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/file_store_failed_tx_store.js +34 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts +4 -0
- package/dest/publisher/l1_tx_failed_store/index.d.ts.map +1 -0
- package/dest/publisher/l1_tx_failed_store/index.js +2 -0
- package/dest/publisher/sequencer-publisher-factory.d.ts +15 -6
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +28 -3
- package/dest/publisher/sequencer-publisher-metrics.d.ts +3 -3
- package/dest/publisher/sequencer-publisher-metrics.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-metrics.js +23 -86
- package/dest/publisher/sequencer-publisher.d.ts +79 -49
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +932 -155
- package/dest/sequencer/checkpoint_proposal_job.d.ts +108 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1289 -0
- package/dest/sequencer/checkpoint_voter.d.ts +35 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +109 -0
- package/dest/sequencer/config.d.ts +3 -2
- package/dest/sequencer/config.d.ts.map +1 -1
- package/dest/sequencer/events.d.ts +47 -0
- package/dest/sequencer/events.d.ts.map +1 -0
- package/dest/sequencer/events.js +1 -0
- package/dest/sequencer/index.d.ts +4 -2
- package/dest/sequencer/index.d.ts.map +1 -1
- package/dest/sequencer/index.js +3 -1
- package/dest/sequencer/metrics.d.ts +42 -6
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +227 -72
- package/dest/sequencer/sequencer.d.ts +125 -134
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +754 -652
- package/dest/sequencer/timetable.d.ts +54 -16
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +147 -62
- package/dest/sequencer/types.d.ts +3 -0
- package/dest/sequencer/types.d.ts.map +1 -0
- package/dest/sequencer/types.js +1 -0
- package/dest/sequencer/utils.d.ts +14 -8
- package/dest/sequencer/utils.d.ts.map +1 -1
- package/dest/sequencer/utils.js +7 -4
- package/dest/test/index.d.ts +6 -7
- package/dest/test/index.d.ts.map +1 -1
- package/dest/test/mock_checkpoint_builder.d.ts +95 -0
- package/dest/test/mock_checkpoint_builder.d.ts.map +1 -0
- package/dest/test/mock_checkpoint_builder.js +231 -0
- package/dest/test/utils.d.ts +53 -0
- package/dest/test/utils.d.ts.map +1 -0
- package/dest/test/utils.js +104 -0
- package/package.json +31 -30
- package/src/client/sequencer-client.ts +97 -55
- package/src/config.ts +124 -53
- package/src/global_variable_builder/global_builder.ts +76 -73
- package/src/global_variable_builder/index.ts +1 -1
- package/src/index.ts +1 -7
- package/src/publisher/config.ts +163 -50
- package/src/publisher/index.ts +3 -0
- package/src/publisher/l1_tx_failed_store/factory.ts +32 -0
- package/src/publisher/l1_tx_failed_store/failed_tx_store.ts +55 -0
- package/src/publisher/l1_tx_failed_store/file_store_failed_tx_store.ts +46 -0
- package/src/publisher/l1_tx_failed_store/index.ts +3 -0
- package/src/publisher/sequencer-publisher-factory.ts +43 -10
- package/src/publisher/sequencer-publisher-metrics.ts +19 -71
- package/src/publisher/sequencer-publisher.ts +635 -200
- package/src/sequencer/README.md +531 -0
- package/src/sequencer/checkpoint_proposal_job.ts +1049 -0
- package/src/sequencer/checkpoint_voter.ts +130 -0
- package/src/sequencer/config.ts +2 -1
- package/src/sequencer/events.ts +27 -0
- package/src/sequencer/index.ts +3 -1
- package/src/sequencer/metrics.ts +282 -82
- package/src/sequencer/sequencer.ts +521 -859
- package/src/sequencer/timetable.ts +178 -83
- package/src/sequencer/types.ts +6 -0
- package/src/sequencer/utils.ts +18 -9
- package/src/test/index.ts +5 -6
- package/src/test/mock_checkpoint_builder.ts +323 -0
- package/src/test/utils.ts +167 -0
- package/dest/sequencer/block_builder.d.ts +0 -27
- package/dest/sequencer/block_builder.d.ts.map +0 -1
- package/dest/sequencer/block_builder.js +0 -134
- package/dest/tx_validator/nullifier_cache.d.ts +0 -14
- package/dest/tx_validator/nullifier_cache.d.ts.map +0 -1
- package/dest/tx_validator/nullifier_cache.js +0 -24
- package/dest/tx_validator/tx_validator_factory.d.ts +0 -17
- package/dest/tx_validator/tx_validator_factory.d.ts.map +0 -1
- package/dest/tx_validator/tx_validator_factory.js +0 -53
- package/src/sequencer/block_builder.ts +0 -222
- package/src/tx_validator/nullifier_cache.ts +0 -30
- package/src/tx_validator/tx_validator_factory.ts +0 -132
|
@@ -1,19 +1,400 @@
|
|
|
1
|
+
function applyDecs2203RFactory() {
|
|
2
|
+
function createAddInitializerMethod(initializers, decoratorFinishedRef) {
|
|
3
|
+
return function addInitializer(initializer) {
|
|
4
|
+
assertNotFinished(decoratorFinishedRef, "addInitializer");
|
|
5
|
+
assertCallable(initializer, "An initializer");
|
|
6
|
+
initializers.push(initializer);
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value) {
|
|
10
|
+
var kindStr;
|
|
11
|
+
switch(kind){
|
|
12
|
+
case 1:
|
|
13
|
+
kindStr = "accessor";
|
|
14
|
+
break;
|
|
15
|
+
case 2:
|
|
16
|
+
kindStr = "method";
|
|
17
|
+
break;
|
|
18
|
+
case 3:
|
|
19
|
+
kindStr = "getter";
|
|
20
|
+
break;
|
|
21
|
+
case 4:
|
|
22
|
+
kindStr = "setter";
|
|
23
|
+
break;
|
|
24
|
+
default:
|
|
25
|
+
kindStr = "field";
|
|
26
|
+
}
|
|
27
|
+
var ctx = {
|
|
28
|
+
kind: kindStr,
|
|
29
|
+
name: isPrivate ? "#" + name : name,
|
|
30
|
+
static: isStatic,
|
|
31
|
+
private: isPrivate,
|
|
32
|
+
metadata: metadata
|
|
33
|
+
};
|
|
34
|
+
var decoratorFinishedRef = {
|
|
35
|
+
v: false
|
|
36
|
+
};
|
|
37
|
+
ctx.addInitializer = createAddInitializerMethod(initializers, decoratorFinishedRef);
|
|
38
|
+
var get, set;
|
|
39
|
+
if (kind === 0) {
|
|
40
|
+
if (isPrivate) {
|
|
41
|
+
get = desc.get;
|
|
42
|
+
set = desc.set;
|
|
43
|
+
} else {
|
|
44
|
+
get = function() {
|
|
45
|
+
return this[name];
|
|
46
|
+
};
|
|
47
|
+
set = function(v) {
|
|
48
|
+
this[name] = v;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
} else if (kind === 2) {
|
|
52
|
+
get = function() {
|
|
53
|
+
return desc.value;
|
|
54
|
+
};
|
|
55
|
+
} else {
|
|
56
|
+
if (kind === 1 || kind === 3) {
|
|
57
|
+
get = function() {
|
|
58
|
+
return desc.get.call(this);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
if (kind === 1 || kind === 4) {
|
|
62
|
+
set = function(v) {
|
|
63
|
+
desc.set.call(this, v);
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
ctx.access = get && set ? {
|
|
68
|
+
get: get,
|
|
69
|
+
set: set
|
|
70
|
+
} : get ? {
|
|
71
|
+
get: get
|
|
72
|
+
} : {
|
|
73
|
+
set: set
|
|
74
|
+
};
|
|
75
|
+
try {
|
|
76
|
+
return dec(value, ctx);
|
|
77
|
+
} finally{
|
|
78
|
+
decoratorFinishedRef.v = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function assertNotFinished(decoratorFinishedRef, fnName) {
|
|
82
|
+
if (decoratorFinishedRef.v) {
|
|
83
|
+
throw new Error("attempted to call " + fnName + " after decoration was finished");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function assertCallable(fn, hint) {
|
|
87
|
+
if (typeof fn !== "function") {
|
|
88
|
+
throw new TypeError(hint + " must be a function");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function assertValidReturnValue(kind, value) {
|
|
92
|
+
var type = typeof value;
|
|
93
|
+
if (kind === 1) {
|
|
94
|
+
if (type !== "object" || value === null) {
|
|
95
|
+
throw new TypeError("accessor decorators must return an object with get, set, or init properties or void 0");
|
|
96
|
+
}
|
|
97
|
+
if (value.get !== undefined) {
|
|
98
|
+
assertCallable(value.get, "accessor.get");
|
|
99
|
+
}
|
|
100
|
+
if (value.set !== undefined) {
|
|
101
|
+
assertCallable(value.set, "accessor.set");
|
|
102
|
+
}
|
|
103
|
+
if (value.init !== undefined) {
|
|
104
|
+
assertCallable(value.init, "accessor.init");
|
|
105
|
+
}
|
|
106
|
+
} else if (type !== "function") {
|
|
107
|
+
var hint;
|
|
108
|
+
if (kind === 0) {
|
|
109
|
+
hint = "field";
|
|
110
|
+
} else if (kind === 10) {
|
|
111
|
+
hint = "class";
|
|
112
|
+
} else {
|
|
113
|
+
hint = "method";
|
|
114
|
+
}
|
|
115
|
+
throw new TypeError(hint + " decorators must return a function or void 0");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata) {
|
|
119
|
+
var decs = decInfo[0];
|
|
120
|
+
var desc, init, value;
|
|
121
|
+
if (isPrivate) {
|
|
122
|
+
if (kind === 0 || kind === 1) {
|
|
123
|
+
desc = {
|
|
124
|
+
get: decInfo[3],
|
|
125
|
+
set: decInfo[4]
|
|
126
|
+
};
|
|
127
|
+
} else if (kind === 3) {
|
|
128
|
+
desc = {
|
|
129
|
+
get: decInfo[3]
|
|
130
|
+
};
|
|
131
|
+
} else if (kind === 4) {
|
|
132
|
+
desc = {
|
|
133
|
+
set: decInfo[3]
|
|
134
|
+
};
|
|
135
|
+
} else {
|
|
136
|
+
desc = {
|
|
137
|
+
value: decInfo[3]
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
} else if (kind !== 0) {
|
|
141
|
+
desc = Object.getOwnPropertyDescriptor(base, name);
|
|
142
|
+
}
|
|
143
|
+
if (kind === 1) {
|
|
144
|
+
value = {
|
|
145
|
+
get: desc.get,
|
|
146
|
+
set: desc.set
|
|
147
|
+
};
|
|
148
|
+
} else if (kind === 2) {
|
|
149
|
+
value = desc.value;
|
|
150
|
+
} else if (kind === 3) {
|
|
151
|
+
value = desc.get;
|
|
152
|
+
} else if (kind === 4) {
|
|
153
|
+
value = desc.set;
|
|
154
|
+
}
|
|
155
|
+
var newValue, get, set;
|
|
156
|
+
if (typeof decs === "function") {
|
|
157
|
+
newValue = memberDec(decs, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
|
|
158
|
+
if (newValue !== void 0) {
|
|
159
|
+
assertValidReturnValue(kind, newValue);
|
|
160
|
+
if (kind === 0) {
|
|
161
|
+
init = newValue;
|
|
162
|
+
} else if (kind === 1) {
|
|
163
|
+
init = newValue.init;
|
|
164
|
+
get = newValue.get || value.get;
|
|
165
|
+
set = newValue.set || value.set;
|
|
166
|
+
value = {
|
|
167
|
+
get: get,
|
|
168
|
+
set: set
|
|
169
|
+
};
|
|
170
|
+
} else {
|
|
171
|
+
value = newValue;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
for(var i = decs.length - 1; i >= 0; i--){
|
|
176
|
+
var dec = decs[i];
|
|
177
|
+
newValue = memberDec(dec, name, desc, initializers, kind, isStatic, isPrivate, metadata, value);
|
|
178
|
+
if (newValue !== void 0) {
|
|
179
|
+
assertValidReturnValue(kind, newValue);
|
|
180
|
+
var newInit;
|
|
181
|
+
if (kind === 0) {
|
|
182
|
+
newInit = newValue;
|
|
183
|
+
} else if (kind === 1) {
|
|
184
|
+
newInit = newValue.init;
|
|
185
|
+
get = newValue.get || value.get;
|
|
186
|
+
set = newValue.set || value.set;
|
|
187
|
+
value = {
|
|
188
|
+
get: get,
|
|
189
|
+
set: set
|
|
190
|
+
};
|
|
191
|
+
} else {
|
|
192
|
+
value = newValue;
|
|
193
|
+
}
|
|
194
|
+
if (newInit !== void 0) {
|
|
195
|
+
if (init === void 0) {
|
|
196
|
+
init = newInit;
|
|
197
|
+
} else if (typeof init === "function") {
|
|
198
|
+
init = [
|
|
199
|
+
init,
|
|
200
|
+
newInit
|
|
201
|
+
];
|
|
202
|
+
} else {
|
|
203
|
+
init.push(newInit);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (kind === 0 || kind === 1) {
|
|
210
|
+
if (init === void 0) {
|
|
211
|
+
init = function(instance, init) {
|
|
212
|
+
return init;
|
|
213
|
+
};
|
|
214
|
+
} else if (typeof init !== "function") {
|
|
215
|
+
var ownInitializers = init;
|
|
216
|
+
init = function(instance, init) {
|
|
217
|
+
var value = init;
|
|
218
|
+
for(var i = 0; i < ownInitializers.length; i++){
|
|
219
|
+
value = ownInitializers[i].call(instance, value);
|
|
220
|
+
}
|
|
221
|
+
return value;
|
|
222
|
+
};
|
|
223
|
+
} else {
|
|
224
|
+
var originalInitializer = init;
|
|
225
|
+
init = function(instance, init) {
|
|
226
|
+
return originalInitializer.call(instance, init);
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
ret.push(init);
|
|
230
|
+
}
|
|
231
|
+
if (kind !== 0) {
|
|
232
|
+
if (kind === 1) {
|
|
233
|
+
desc.get = value.get;
|
|
234
|
+
desc.set = value.set;
|
|
235
|
+
} else if (kind === 2) {
|
|
236
|
+
desc.value = value;
|
|
237
|
+
} else if (kind === 3) {
|
|
238
|
+
desc.get = value;
|
|
239
|
+
} else if (kind === 4) {
|
|
240
|
+
desc.set = value;
|
|
241
|
+
}
|
|
242
|
+
if (isPrivate) {
|
|
243
|
+
if (kind === 1) {
|
|
244
|
+
ret.push(function(instance, args) {
|
|
245
|
+
return value.get.call(instance, args);
|
|
246
|
+
});
|
|
247
|
+
ret.push(function(instance, args) {
|
|
248
|
+
return value.set.call(instance, args);
|
|
249
|
+
});
|
|
250
|
+
} else if (kind === 2) {
|
|
251
|
+
ret.push(value);
|
|
252
|
+
} else {
|
|
253
|
+
ret.push(function(instance, args) {
|
|
254
|
+
return value.call(instance, args);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
Object.defineProperty(base, name, desc);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
function applyMemberDecs(Class, decInfos, metadata) {
|
|
263
|
+
var ret = [];
|
|
264
|
+
var protoInitializers;
|
|
265
|
+
var staticInitializers;
|
|
266
|
+
var existingProtoNonFields = new Map();
|
|
267
|
+
var existingStaticNonFields = new Map();
|
|
268
|
+
for(var i = 0; i < decInfos.length; i++){
|
|
269
|
+
var decInfo = decInfos[i];
|
|
270
|
+
if (!Array.isArray(decInfo)) continue;
|
|
271
|
+
var kind = decInfo[1];
|
|
272
|
+
var name = decInfo[2];
|
|
273
|
+
var isPrivate = decInfo.length > 3;
|
|
274
|
+
var isStatic = kind >= 5;
|
|
275
|
+
var base;
|
|
276
|
+
var initializers;
|
|
277
|
+
if (isStatic) {
|
|
278
|
+
base = Class;
|
|
279
|
+
kind = kind - 5;
|
|
280
|
+
staticInitializers = staticInitializers || [];
|
|
281
|
+
initializers = staticInitializers;
|
|
282
|
+
} else {
|
|
283
|
+
base = Class.prototype;
|
|
284
|
+
protoInitializers = protoInitializers || [];
|
|
285
|
+
initializers = protoInitializers;
|
|
286
|
+
}
|
|
287
|
+
if (kind !== 0 && !isPrivate) {
|
|
288
|
+
var existingNonFields = isStatic ? existingStaticNonFields : existingProtoNonFields;
|
|
289
|
+
var existingKind = existingNonFields.get(name) || 0;
|
|
290
|
+
if (existingKind === true || existingKind === 3 && kind !== 4 || existingKind === 4 && kind !== 3) {
|
|
291
|
+
throw new Error("Attempted to decorate a public method/accessor that has the same name as a previously decorated public method/accessor. This is not currently supported by the decorators plugin. Property name was: " + name);
|
|
292
|
+
} else if (!existingKind && kind > 2) {
|
|
293
|
+
existingNonFields.set(name, kind);
|
|
294
|
+
} else {
|
|
295
|
+
existingNonFields.set(name, true);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
applyMemberDec(ret, base, decInfo, name, kind, isStatic, isPrivate, initializers, metadata);
|
|
299
|
+
}
|
|
300
|
+
pushInitializers(ret, protoInitializers);
|
|
301
|
+
pushInitializers(ret, staticInitializers);
|
|
302
|
+
return ret;
|
|
303
|
+
}
|
|
304
|
+
function pushInitializers(ret, initializers) {
|
|
305
|
+
if (initializers) {
|
|
306
|
+
ret.push(function(instance) {
|
|
307
|
+
for(var i = 0; i < initializers.length; i++){
|
|
308
|
+
initializers[i].call(instance);
|
|
309
|
+
}
|
|
310
|
+
return instance;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function applyClassDecs(targetClass, classDecs, metadata) {
|
|
315
|
+
if (classDecs.length > 0) {
|
|
316
|
+
var initializers = [];
|
|
317
|
+
var newClass = targetClass;
|
|
318
|
+
var name = targetClass.name;
|
|
319
|
+
for(var i = classDecs.length - 1; i >= 0; i--){
|
|
320
|
+
var decoratorFinishedRef = {
|
|
321
|
+
v: false
|
|
322
|
+
};
|
|
323
|
+
try {
|
|
324
|
+
var nextNewClass = classDecs[i](newClass, {
|
|
325
|
+
kind: "class",
|
|
326
|
+
name: name,
|
|
327
|
+
addInitializer: createAddInitializerMethod(initializers, decoratorFinishedRef),
|
|
328
|
+
metadata
|
|
329
|
+
});
|
|
330
|
+
} finally{
|
|
331
|
+
decoratorFinishedRef.v = true;
|
|
332
|
+
}
|
|
333
|
+
if (nextNewClass !== undefined) {
|
|
334
|
+
assertValidReturnValue(10, nextNewClass);
|
|
335
|
+
newClass = nextNewClass;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return [
|
|
339
|
+
defineMetadata(newClass, metadata),
|
|
340
|
+
function() {
|
|
341
|
+
for(var i = 0; i < initializers.length; i++){
|
|
342
|
+
initializers[i].call(newClass);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function defineMetadata(Class, metadata) {
|
|
349
|
+
return Object.defineProperty(Class, Symbol.metadata || Symbol.for("Symbol.metadata"), {
|
|
350
|
+
configurable: true,
|
|
351
|
+
enumerable: true,
|
|
352
|
+
value: metadata
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
return function applyDecs2203R(targetClass, memberDecs, classDecs, parentClass) {
|
|
356
|
+
if (parentClass !== void 0) {
|
|
357
|
+
var parentMetadata = parentClass[Symbol.metadata || Symbol.for("Symbol.metadata")];
|
|
358
|
+
}
|
|
359
|
+
var metadata = Object.create(parentMetadata === void 0 ? null : parentMetadata);
|
|
360
|
+
var e = applyMemberDecs(targetClass, memberDecs, metadata);
|
|
361
|
+
if (!classDecs.length) defineMetadata(targetClass, metadata);
|
|
362
|
+
return {
|
|
363
|
+
e: e,
|
|
364
|
+
get c () {
|
|
365
|
+
return applyClassDecs(targetClass, classDecs, metadata);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function _apply_decs_2203_r(targetClass, memberDecs, classDecs, parentClass) {
|
|
371
|
+
return (_apply_decs_2203_r = applyDecs2203RFactory())(targetClass, memberDecs, classDecs, parentClass);
|
|
372
|
+
}
|
|
373
|
+
var _dec, _dec1, _dec2, _initProto;
|
|
1
374
|
import { Blob, getBlobsPerL1Block, getPrefixedEthBlobCommitments } from '@aztec/blob-lib';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
375
|
+
import { FeeAssetPriceOracle, MULTI_CALL_3_ADDRESS, Multicall3, RollupContract } from '@aztec/ethereum/contracts';
|
|
376
|
+
import { L1FeeAnalyzer } from '@aztec/ethereum/l1-fee-analysis';
|
|
377
|
+
import { MAX_L1_TX_LIMIT, WEI_CONST } from '@aztec/ethereum/l1-tx-utils';
|
|
378
|
+
import { FormattedViemError, formatViemError, mergeAbis, tryExtractEvent } from '@aztec/ethereum/utils';
|
|
4
379
|
import { sumBigint } from '@aztec/foundation/bigint';
|
|
5
380
|
import { toHex as toPaddedHex } from '@aztec/foundation/bigint-buffer';
|
|
6
|
-
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
381
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
382
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
383
|
+
import { pick } from '@aztec/foundation/collection';
|
|
384
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
7
385
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
386
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
9
387
|
import { createLogger } from '@aztec/foundation/log';
|
|
388
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
10
389
|
import { bufferToHex } from '@aztec/foundation/string';
|
|
11
390
|
import { Timer } from '@aztec/foundation/timer';
|
|
12
391
|
import { EmpireBaseAbi, ErrorsAbi, RollupAbi } from '@aztec/l1-artifacts';
|
|
13
392
|
import { encodeSlashConsensusVotes } from '@aztec/slasher';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
393
|
+
import { CommitteeAttestationsAndSigners } from '@aztec/stdlib/block';
|
|
394
|
+
import { getNextL1SlotTimestamp } from '@aztec/stdlib/epoch-helpers';
|
|
395
|
+
import { getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
396
|
+
import { encodeFunctionData, keccak256, multicall3Abi, toHex } from 'viem';
|
|
397
|
+
import { createL1TxFailedStore } from './l1_tx_failed_store/index.js';
|
|
17
398
|
import { SequencerPublisherMetrics } from './sequencer-publisher-metrics.js';
|
|
18
399
|
export const Actions = [
|
|
19
400
|
'invalidate-by-invalid-attestation',
|
|
@@ -28,22 +409,46 @@ export const Actions = [
|
|
|
28
409
|
];
|
|
29
410
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
30
411
|
export const compareActions = (a, b)=>Actions.indexOf(a) - Actions.indexOf(b);
|
|
412
|
+
_dec = trackSpan('SequencerPublisher.sendRequests'), _dec1 = trackSpan('SequencerPublisher.validateBlockHeader'), _dec2 = trackSpan('SequencerPublisher.validateCheckpointForSubmission');
|
|
31
413
|
export class SequencerPublisher {
|
|
32
414
|
config;
|
|
415
|
+
static{
|
|
416
|
+
({ e: [_initProto] } = _apply_decs_2203_r(this, [
|
|
417
|
+
[
|
|
418
|
+
_dec,
|
|
419
|
+
2,
|
|
420
|
+
"sendRequests"
|
|
421
|
+
],
|
|
422
|
+
[
|
|
423
|
+
_dec1,
|
|
424
|
+
2,
|
|
425
|
+
"validateBlockHeader"
|
|
426
|
+
],
|
|
427
|
+
[
|
|
428
|
+
_dec2,
|
|
429
|
+
2,
|
|
430
|
+
"validateCheckpointForSubmission"
|
|
431
|
+
]
|
|
432
|
+
], []));
|
|
433
|
+
}
|
|
33
434
|
interrupted;
|
|
34
435
|
metrics;
|
|
35
436
|
epochCache;
|
|
437
|
+
failedTxStore;
|
|
36
438
|
governanceLog;
|
|
37
439
|
slashingLog;
|
|
38
440
|
lastActions;
|
|
441
|
+
isPayloadEmptyCache;
|
|
442
|
+
payloadProposedCache;
|
|
39
443
|
log;
|
|
40
444
|
ethereumSlotDuration;
|
|
41
|
-
|
|
445
|
+
aztecSlotDuration;
|
|
446
|
+
dateProvider;
|
|
447
|
+
blobClient;
|
|
42
448
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
static PROPOSE_GAS_GUESS = 12_000_000n;
|
|
449
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
|
|
450
|
+
/** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
|
|
451
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
|
|
47
452
|
// A CALL to a cold address is 2700 gas
|
|
48
453
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
49
454
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -53,24 +458,29 @@ export class SequencerPublisher {
|
|
|
53
458
|
govProposerContract;
|
|
54
459
|
slashingProposerContract;
|
|
55
460
|
slashFactoryContract;
|
|
461
|
+
tracer;
|
|
56
462
|
requests;
|
|
57
463
|
constructor(config, deps){
|
|
58
464
|
this.config = config;
|
|
59
|
-
this.interrupted = false;
|
|
465
|
+
this.interrupted = (_initProto(this), false);
|
|
60
466
|
this.governanceLog = createLogger('sequencer:publisher:governance');
|
|
61
467
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
62
468
|
this.lastActions = {};
|
|
469
|
+
this.isPayloadEmptyCache = new Map();
|
|
470
|
+
this.payloadProposedCache = new Set();
|
|
63
471
|
this.requests = [];
|
|
64
472
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
65
473
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
474
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
475
|
+
this.dateProvider = deps.dateProvider;
|
|
66
476
|
this.epochCache = deps.epochCache;
|
|
67
477
|
this.lastActions = deps.lastActions;
|
|
68
|
-
this.
|
|
69
|
-
logger: createLogger('sequencer:blob-sink:client')
|
|
70
|
-
});
|
|
478
|
+
this.blobClient = deps.blobClient;
|
|
71
479
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
72
480
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
481
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
73
482
|
this.l1TxUtils = deps.l1TxUtils;
|
|
483
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
74
484
|
this.rollupContract = deps.rollupContract;
|
|
75
485
|
this.govProposerContract = deps.governanceProposerContract;
|
|
76
486
|
this.slashingProposerContract = deps.slashingProposerContract;
|
|
@@ -80,14 +490,49 @@ export class SequencerPublisher {
|
|
|
80
490
|
this.slashingProposerContract = newSlashingProposer;
|
|
81
491
|
});
|
|
82
492
|
this.slashFactoryContract = deps.slashFactoryContract;
|
|
493
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
494
|
+
if (config.fishermanMode) {
|
|
495
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
496
|
+
}
|
|
497
|
+
// Initialize fee asset price oracle
|
|
498
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
|
|
499
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
500
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
504
|
+
* Does nothing if no store is configured.
|
|
505
|
+
*/ backupFailedTx(failedTx) {
|
|
506
|
+
if (!this.failedTxStore) {
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const tx = {
|
|
510
|
+
...failedTx,
|
|
511
|
+
timestamp: Date.now()
|
|
512
|
+
};
|
|
513
|
+
// Fire and forget - don't block on backup
|
|
514
|
+
void this.failedTxStore.then((store)=>store?.saveFailedTx(tx)).catch((err)=>{
|
|
515
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
516
|
+
});
|
|
83
517
|
}
|
|
84
518
|
getRollupContract() {
|
|
85
519
|
return this.rollupContract;
|
|
86
520
|
}
|
|
521
|
+
/**
|
|
522
|
+
* Gets the fee asset price modifier from the oracle.
|
|
523
|
+
* Returns 0n if the oracle query fails.
|
|
524
|
+
*/ getFeeAssetPriceModifier() {
|
|
525
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
526
|
+
}
|
|
87
527
|
getSenderAddress() {
|
|
88
528
|
return this.l1TxUtils.getSenderAddress();
|
|
89
529
|
}
|
|
90
530
|
/**
|
|
531
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
532
|
+
*/ getL1FeeAnalyzer() {
|
|
533
|
+
return this.l1FeeAnalyzer;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
91
536
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
92
537
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
93
538
|
*/ setProposerAddressForSimulation(proposerAddress) {
|
|
@@ -97,7 +542,7 @@ export class SequencerPublisher {
|
|
|
97
542
|
this.requests.push(request);
|
|
98
543
|
}
|
|
99
544
|
getCurrentL2Slot() {
|
|
100
|
-
return this.epochCache.
|
|
545
|
+
return this.epochCache.getSlotNow();
|
|
101
546
|
}
|
|
102
547
|
/**
|
|
103
548
|
* Clears all pending requests without sending them.
|
|
@@ -109,6 +554,46 @@ export class SequencerPublisher {
|
|
|
109
554
|
}
|
|
110
555
|
}
|
|
111
556
|
/**
|
|
557
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
558
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
559
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
560
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
561
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
562
|
+
*/ async analyzeL1Fees(l2SlotNumber, onComplete) {
|
|
563
|
+
if (!this.l1FeeAnalyzer) {
|
|
564
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
565
|
+
return undefined;
|
|
566
|
+
}
|
|
567
|
+
const requestsToAnalyze = [
|
|
568
|
+
...this.requests
|
|
569
|
+
];
|
|
570
|
+
if (requestsToAnalyze.length === 0) {
|
|
571
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
574
|
+
// Extract blob config from requests (if any)
|
|
575
|
+
const blobConfigs = requestsToAnalyze.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
|
|
576
|
+
const blobConfig = blobConfigs[0];
|
|
577
|
+
// Get gas configs
|
|
578
|
+
const gasConfigs = requestsToAnalyze.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
|
|
579
|
+
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
580
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g)=>sum + g, 0n) : 0n;
|
|
581
|
+
// Get the transaction requests
|
|
582
|
+
const l1Requests = requestsToAnalyze.map((r)=>r.request);
|
|
583
|
+
// Start the analysis
|
|
584
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
|
|
585
|
+
this.log.info('Started L1 fee analysis', {
|
|
586
|
+
analysisId,
|
|
587
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
588
|
+
requestCount: requestsToAnalyze.length,
|
|
589
|
+
hasBlobConfig: !!blobConfig,
|
|
590
|
+
gasLimit: gasLimit.toString(),
|
|
591
|
+
actions: requestsToAnalyze.map((r)=>r.action)
|
|
592
|
+
});
|
|
593
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
594
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
112
597
|
* Sends all requests that are still valid.
|
|
113
598
|
* @returns one of:
|
|
114
599
|
* - A receipt and stats if the tx succeeded
|
|
@@ -119,7 +604,7 @@ export class SequencerPublisher {
|
|
|
119
604
|
...this.requests
|
|
120
605
|
];
|
|
121
606
|
this.requests = [];
|
|
122
|
-
if (this.interrupted) {
|
|
607
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
123
608
|
return undefined;
|
|
124
609
|
}
|
|
125
610
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -146,15 +631,24 @@ export class SequencerPublisher {
|
|
|
146
631
|
// @note - we can only have one blob config per bundle
|
|
147
632
|
// find requests with gas and blob configs
|
|
148
633
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
149
|
-
const gasConfigs =
|
|
150
|
-
const blobConfigs =
|
|
634
|
+
const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
|
|
635
|
+
const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
|
|
151
636
|
if (blobConfigs.length > 1) {
|
|
152
637
|
throw new Error('Multiple blob configs found');
|
|
153
638
|
}
|
|
154
639
|
const blobConfig = blobConfigs[0];
|
|
155
640
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
156
641
|
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
157
|
-
|
|
642
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
643
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
644
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
645
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
646
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
647
|
+
requested: gasLimit,
|
|
648
|
+
capped: maxGas
|
|
649
|
+
});
|
|
650
|
+
gasLimit = maxGas;
|
|
651
|
+
}
|
|
158
652
|
const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
|
|
159
653
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
|
|
160
654
|
const txConfig = {
|
|
@@ -165,12 +659,34 @@ export class SequencerPublisher {
|
|
|
165
659
|
// This ensures the committee gets precomputed correctly
|
|
166
660
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
167
661
|
try {
|
|
662
|
+
// Capture context for failed tx backup before sending
|
|
663
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
664
|
+
const multicallData = encodeFunctionData({
|
|
665
|
+
abi: multicall3Abi,
|
|
666
|
+
functionName: 'aggregate3',
|
|
667
|
+
args: [
|
|
668
|
+
validRequests.map((r)=>({
|
|
669
|
+
target: r.request.to,
|
|
670
|
+
callData: r.request.data,
|
|
671
|
+
allowFailure: true
|
|
672
|
+
}))
|
|
673
|
+
]
|
|
674
|
+
});
|
|
675
|
+
const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
|
|
676
|
+
const txContext = {
|
|
677
|
+
multicallData,
|
|
678
|
+
blobData: blobDataHex,
|
|
679
|
+
l1BlockNumber
|
|
680
|
+
};
|
|
168
681
|
this.log.debug('Forwarding transactions', {
|
|
169
682
|
validRequests: validRequests.map((request)=>request.action),
|
|
170
683
|
txConfig
|
|
171
684
|
});
|
|
172
|
-
const result = await
|
|
173
|
-
|
|
685
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
686
|
+
if (result === undefined) {
|
|
687
|
+
return undefined;
|
|
688
|
+
}
|
|
689
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
|
|
174
690
|
return {
|
|
175
691
|
result,
|
|
176
692
|
expiredActions,
|
|
@@ -190,17 +706,83 @@ export class SequencerPublisher {
|
|
|
190
706
|
}
|
|
191
707
|
}
|
|
192
708
|
}
|
|
193
|
-
|
|
709
|
+
/**
|
|
710
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
711
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
712
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
713
|
+
*/ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
|
|
714
|
+
const triedAddresses = [];
|
|
715
|
+
let currentPublisher = this.l1TxUtils;
|
|
716
|
+
while(true){
|
|
717
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
718
|
+
try {
|
|
719
|
+
const result = await Multicall3.forward(validRequests.map((r)=>r.request), currentPublisher, txConfig, blobConfig, this.rollupContract.address, this.log);
|
|
720
|
+
this.l1TxUtils = currentPublisher;
|
|
721
|
+
return result;
|
|
722
|
+
} catch (err) {
|
|
723
|
+
if (err instanceof TimeoutError) {
|
|
724
|
+
throw err;
|
|
725
|
+
}
|
|
726
|
+
const viemError = formatViemError(err);
|
|
727
|
+
if (!this.getNextPublisher) {
|
|
728
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
729
|
+
return undefined;
|
|
730
|
+
}
|
|
731
|
+
this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
|
|
732
|
+
const nextPublisher = await this.getNextPublisher([
|
|
733
|
+
...triedAddresses
|
|
734
|
+
]);
|
|
735
|
+
if (!nextPublisher) {
|
|
736
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
737
|
+
return undefined;
|
|
738
|
+
}
|
|
739
|
+
currentPublisher = nextPublisher;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
callbackBundledTransactions(requests, result, txContext) {
|
|
194
744
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
195
745
|
if (result instanceof FormattedViemError) {
|
|
196
746
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
747
|
+
this.backupFailedTx({
|
|
748
|
+
id: keccak256(txContext.multicallData),
|
|
749
|
+
failureType: 'send-error',
|
|
750
|
+
request: {
|
|
751
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
752
|
+
data: txContext.multicallData
|
|
753
|
+
},
|
|
754
|
+
blobData: txContext.blobData,
|
|
755
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
756
|
+
error: {
|
|
757
|
+
message: result.message,
|
|
758
|
+
name: result.name
|
|
759
|
+
},
|
|
760
|
+
context: {
|
|
761
|
+
actions: requests.map((r)=>r.action),
|
|
762
|
+
requests: requests.map((r)=>({
|
|
763
|
+
action: r.action,
|
|
764
|
+
to: r.request.to,
|
|
765
|
+
data: r.request.data
|
|
766
|
+
})),
|
|
767
|
+
sender: this.getSenderAddress().toString()
|
|
768
|
+
}
|
|
769
|
+
});
|
|
197
770
|
return {
|
|
198
771
|
failedActions: requests.map((r)=>r.action)
|
|
199
772
|
};
|
|
200
773
|
} else {
|
|
201
774
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
202
775
|
result,
|
|
203
|
-
requests
|
|
776
|
+
requests: requests.map((r)=>({
|
|
777
|
+
...r,
|
|
778
|
+
// Avoid logging large blob data
|
|
779
|
+
blobConfig: r.blobConfig ? {
|
|
780
|
+
...r.blobConfig,
|
|
781
|
+
blobs: r.blobConfig.blobs.map((b)=>({
|
|
782
|
+
size: trimmedBytesLength(b)
|
|
783
|
+
}))
|
|
784
|
+
} : undefined
|
|
785
|
+
}))
|
|
204
786
|
});
|
|
205
787
|
const successfulActions = [];
|
|
206
788
|
const failedActions = [];
|
|
@@ -211,6 +793,37 @@ export class SequencerPublisher {
|
|
|
211
793
|
failedActions.push(request.action);
|
|
212
794
|
}
|
|
213
795
|
}
|
|
796
|
+
// Single backup for the whole reverted tx
|
|
797
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
798
|
+
this.backupFailedTx({
|
|
799
|
+
id: result.receipt.transactionHash,
|
|
800
|
+
failureType: 'revert',
|
|
801
|
+
request: {
|
|
802
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
803
|
+
data: txContext.multicallData
|
|
804
|
+
},
|
|
805
|
+
blobData: txContext.blobData,
|
|
806
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
807
|
+
receipt: {
|
|
808
|
+
transactionHash: result.receipt.transactionHash,
|
|
809
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
810
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
811
|
+
status: 'reverted'
|
|
812
|
+
},
|
|
813
|
+
error: {
|
|
814
|
+
message: result.errorMsg ?? 'Transaction reverted'
|
|
815
|
+
},
|
|
816
|
+
context: {
|
|
817
|
+
actions: failedActions,
|
|
818
|
+
requests: requests.filter((r)=>failedActions.includes(r.action)).map((r)=>({
|
|
819
|
+
action: r.action,
|
|
820
|
+
to: r.request.to,
|
|
821
|
+
data: r.request.data
|
|
822
|
+
})),
|
|
823
|
+
sender: this.getSenderAddress().toString()
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
}
|
|
214
827
|
return {
|
|
215
828
|
successfulActions,
|
|
216
829
|
failedActions
|
|
@@ -218,18 +831,21 @@ export class SequencerPublisher {
|
|
|
218
831
|
}
|
|
219
832
|
}
|
|
220
833
|
/**
|
|
221
|
-
* @notice Will call `
|
|
834
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
222
835
|
* @param tipArchive - The archive to check
|
|
223
836
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
224
|
-
*/
|
|
837
|
+
*/ async canProposeAt(tipArchive, msgSender, opts = {}) {
|
|
225
838
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
226
839
|
const ignoredErrors = [
|
|
227
840
|
'SlotAlreadyInChain',
|
|
228
841
|
'InvalidProposer',
|
|
229
842
|
'InvalidArchive'
|
|
230
843
|
];
|
|
231
|
-
|
|
232
|
-
|
|
844
|
+
const pipelined = opts.pipelined ?? this.epochCache.isProposerPipeliningEnabled();
|
|
845
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
846
|
+
const nextL1SlotTs = await this.getNextL1SlotTimestampWithL1Floor() + slotOffset;
|
|
847
|
+
return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, {
|
|
848
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
|
|
233
849
|
}).catch((err)=>{
|
|
234
850
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
235
851
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
|
|
@@ -257,11 +873,11 @@ export class SequencerPublisher {
|
|
|
257
873
|
[],
|
|
258
874
|
Signature.empty().toViemSignature(),
|
|
259
875
|
`0x${'0'.repeat(64)}`,
|
|
260
|
-
header.
|
|
876
|
+
header.blobsHash.toString(),
|
|
261
877
|
flags
|
|
262
878
|
];
|
|
263
|
-
const ts =
|
|
264
|
-
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.
|
|
879
|
+
const ts = await this.getNextL1SlotTimestampWithL1Floor();
|
|
880
|
+
const stateOverrides = await this.rollupContract.makePendingCheckpointNumberOverride(opts?.forcePendingCheckpointNumber);
|
|
265
881
|
let balance = 0n;
|
|
266
882
|
if (this.config.fishermanMode) {
|
|
267
883
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -288,34 +904,38 @@ export class SequencerPublisher {
|
|
|
288
904
|
this.log.debug(`Simulated validateHeader`);
|
|
289
905
|
}
|
|
290
906
|
/**
|
|
291
|
-
* Simulate making a call to invalidate a
|
|
292
|
-
* @param
|
|
293
|
-
*/ async
|
|
907
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
908
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
909
|
+
*/ async simulateInvalidateCheckpoint(validationResult) {
|
|
294
910
|
if (validationResult.valid) {
|
|
295
911
|
return undefined;
|
|
296
912
|
}
|
|
297
|
-
const { reason,
|
|
298
|
-
const
|
|
913
|
+
const { reason, checkpoint } = validationResult;
|
|
914
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
299
915
|
const logData = {
|
|
300
|
-
...
|
|
916
|
+
...checkpoint,
|
|
301
917
|
reason
|
|
302
918
|
};
|
|
303
|
-
const
|
|
304
|
-
if (
|
|
305
|
-
this.log.verbose(`Skipping
|
|
306
|
-
|
|
919
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
920
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
921
|
+
this.log.verbose(`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`, {
|
|
922
|
+
currentCheckpointNumber,
|
|
307
923
|
...logData
|
|
308
924
|
});
|
|
309
925
|
return undefined;
|
|
310
926
|
}
|
|
311
|
-
const request = this.
|
|
312
|
-
this.log.debug(`Simulating invalidate
|
|
927
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
928
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, {
|
|
313
929
|
...logData,
|
|
314
930
|
request
|
|
315
931
|
});
|
|
932
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
316
933
|
try {
|
|
317
|
-
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined,
|
|
318
|
-
|
|
934
|
+
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
935
|
+
request.abi ?? [],
|
|
936
|
+
ErrorsAbi
|
|
937
|
+
]));
|
|
938
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
319
939
|
...logData,
|
|
320
940
|
request,
|
|
321
941
|
gasUsed
|
|
@@ -323,90 +943,94 @@ export class SequencerPublisher {
|
|
|
323
943
|
return {
|
|
324
944
|
request,
|
|
325
945
|
gasUsed,
|
|
326
|
-
|
|
327
|
-
|
|
946
|
+
checkpointNumber,
|
|
947
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
328
948
|
reason
|
|
329
949
|
};
|
|
330
950
|
} catch (err) {
|
|
331
951
|
const viemError = formatViemError(err);
|
|
332
|
-
// If the error is due to the
|
|
333
|
-
// we can safely ignore it and return undefined so we go ahead with
|
|
334
|
-
if (viemError.message?.includes('
|
|
335
|
-
this.log.verbose(`Simulation for invalidate
|
|
952
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
953
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
954
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
955
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
|
|
336
956
|
...logData,
|
|
337
957
|
request,
|
|
338
958
|
error: viemError.message
|
|
339
959
|
});
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
342
|
-
this.log.verbose(`
|
|
960
|
+
const latestPendingCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
961
|
+
if (latestPendingCheckpointNumber < checkpointNumber) {
|
|
962
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, {
|
|
343
963
|
...logData
|
|
344
964
|
});
|
|
345
965
|
return undefined;
|
|
346
966
|
} else {
|
|
347
|
-
this.log.error(`Simulation for invalidate ${
|
|
348
|
-
throw new Error(`Failed to simulate invalidate
|
|
967
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`, viemError, logData);
|
|
968
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`, {
|
|
349
969
|
cause: viemError
|
|
350
970
|
});
|
|
351
971
|
}
|
|
352
972
|
}
|
|
353
|
-
// Otherwise, throw. We cannot build the next
|
|
354
|
-
this.log.error(`Simulation for invalidate
|
|
355
|
-
|
|
973
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
974
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
975
|
+
this.backupFailedTx({
|
|
976
|
+
id: keccak256(request.data),
|
|
977
|
+
failureType: 'simulation',
|
|
978
|
+
request: {
|
|
979
|
+
to: request.to,
|
|
980
|
+
data: request.data,
|
|
981
|
+
value: request.value?.toString()
|
|
982
|
+
},
|
|
983
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
984
|
+
error: {
|
|
985
|
+
message: viemError.message,
|
|
986
|
+
name: viemError.name
|
|
987
|
+
},
|
|
988
|
+
context: {
|
|
989
|
+
actions: [
|
|
990
|
+
`invalidate-${reason}`
|
|
991
|
+
],
|
|
992
|
+
checkpointNumber,
|
|
993
|
+
sender: this.getSenderAddress().toString()
|
|
994
|
+
}
|
|
995
|
+
});
|
|
996
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
356
997
|
cause: viemError
|
|
357
998
|
});
|
|
358
999
|
}
|
|
359
1000
|
}
|
|
360
|
-
|
|
1001
|
+
buildInvalidateCheckpointRequest(validationResult) {
|
|
361
1002
|
if (validationResult.valid) {
|
|
362
|
-
throw new Error('Cannot invalidate a valid
|
|
1003
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
363
1004
|
}
|
|
364
|
-
const {
|
|
1005
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
365
1006
|
const logData = {
|
|
366
|
-
...
|
|
1007
|
+
...checkpoint,
|
|
367
1008
|
reason
|
|
368
1009
|
};
|
|
369
|
-
this.log.debug(`
|
|
1010
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
370
1011
|
const attestationsAndSigners = new CommitteeAttestationsAndSigners(validationResult.attestations).getPackedAttestations();
|
|
371
1012
|
if (reason === 'invalid-attestation') {
|
|
372
|
-
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
1013
|
+
return this.rollupContract.buildInvalidateBadAttestationRequest(checkpoint.checkpointNumber, attestationsAndSigners, committee, validationResult.invalidIndex);
|
|
373
1014
|
} else if (reason === 'insufficient-attestations') {
|
|
374
|
-
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
1015
|
+
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(checkpoint.checkpointNumber, attestationsAndSigners, committee);
|
|
375
1016
|
} else {
|
|
376
1017
|
const _ = reason;
|
|
377
1018
|
throw new Error(`Unknown reason for invalidation`);
|
|
378
1019
|
}
|
|
379
1020
|
}
|
|
380
|
-
/**
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
* @param attestationData - The block's attestation data
|
|
387
|
-
*
|
|
388
|
-
*/ async validateBlockForSubmission(block, attestationsAndSigners, attestationsAndSignersSignature, options) {
|
|
389
|
-
const ts = BigInt((await this.l1TxUtils.getBlock()).timestamp + this.ethereumSlotDuration);
|
|
390
|
-
// If we have no attestations, we still need to provide the empty attestations
|
|
391
|
-
// so that the committee is recalculated correctly
|
|
392
|
-
const ignoreSignatures = attestationsAndSigners.attestations.length === 0;
|
|
393
|
-
if (ignoreSignatures) {
|
|
394
|
-
const { committee } = await this.epochCache.getCommittee(block.header.globalVariables.slotNumber);
|
|
395
|
-
if (!committee) {
|
|
396
|
-
this.log.warn(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
397
|
-
throw new Error(`No committee found for slot ${block.header.globalVariables.slotNumber}`);
|
|
398
|
-
}
|
|
399
|
-
attestationsAndSigners.attestations = committee.map((committeeMember)=>CommitteeAttestation.fromAddress(committeeMember));
|
|
400
|
-
}
|
|
401
|
-
const blobFields = block.getCheckpointBlobFields();
|
|
402
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1021
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, options) {
|
|
1022
|
+
// Anchor the simulation timestamp to the checkpoint's own slot start time
|
|
1023
|
+
// rather than the current L1 block timestamp, which may overshoot into the next slot if the build ran late.
|
|
1024
|
+
const ts = checkpoint.header.timestamp;
|
|
1025
|
+
const blobFields = checkpoint.toBlobFields();
|
|
1026
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
403
1027
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
404
1028
|
const args = [
|
|
405
1029
|
{
|
|
406
|
-
header:
|
|
407
|
-
archive: toHex(
|
|
1030
|
+
header: checkpoint.header.toViem(),
|
|
1031
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
408
1032
|
oracleInput: {
|
|
409
|
-
feeAssetPriceModifier:
|
|
1033
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
410
1034
|
}
|
|
411
1035
|
},
|
|
412
1036
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -431,9 +1055,38 @@ export class SequencerPublisher {
|
|
|
431
1055
|
}
|
|
432
1056
|
const round = await base.computeRound(slotNumber);
|
|
433
1057
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
1058
|
+
if (roundInfo.quorumReached) {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
434
1061
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
435
1062
|
return false;
|
|
436
1063
|
}
|
|
1064
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
1065
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
1066
|
+
return false;
|
|
1067
|
+
}
|
|
1068
|
+
// Check if payload was already submitted to governance
|
|
1069
|
+
const cacheKey = payload.toString();
|
|
1070
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1071
|
+
try {
|
|
1072
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1073
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1074
|
+
0,
|
|
1075
|
+
1,
|
|
1076
|
+
2
|
|
1077
|
+
]), this.log, true);
|
|
1078
|
+
if (proposed) {
|
|
1079
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1080
|
+
}
|
|
1081
|
+
} catch (err) {
|
|
1082
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1087
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
437
1090
|
const cachedLastVote = this.lastActions[signalType];
|
|
438
1091
|
this.lastActions[signalType] = slotNumber;
|
|
439
1092
|
const action = signalType;
|
|
@@ -444,15 +1097,41 @@ export class SequencerPublisher {
|
|
|
444
1097
|
signer: this.l1TxUtils.client.account?.address,
|
|
445
1098
|
lastValidL2Slot: slotNumber
|
|
446
1099
|
});
|
|
1100
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
447
1101
|
try {
|
|
448
1102
|
await this.l1TxUtils.simulate(request, {
|
|
449
1103
|
time: timestamp
|
|
450
|
-
}, [],
|
|
1104
|
+
}, [], mergeAbis([
|
|
1105
|
+
request.abi ?? [],
|
|
1106
|
+
ErrorsAbi
|
|
1107
|
+
]));
|
|
451
1108
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
|
|
452
1109
|
request
|
|
453
1110
|
});
|
|
454
1111
|
} catch (err) {
|
|
455
|
-
|
|
1112
|
+
const viemError = formatViemError(err);
|
|
1113
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError);
|
|
1114
|
+
this.backupFailedTx({
|
|
1115
|
+
id: keccak256(request.data),
|
|
1116
|
+
failureType: 'simulation',
|
|
1117
|
+
request: {
|
|
1118
|
+
to: request.to,
|
|
1119
|
+
data: request.data,
|
|
1120
|
+
value: request.value?.toString()
|
|
1121
|
+
},
|
|
1122
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1123
|
+
error: {
|
|
1124
|
+
message: viemError.message,
|
|
1125
|
+
name: viemError.name
|
|
1126
|
+
},
|
|
1127
|
+
context: {
|
|
1128
|
+
actions: [
|
|
1129
|
+
action
|
|
1130
|
+
],
|
|
1131
|
+
slot: slotNumber,
|
|
1132
|
+
sender: this.getSenderAddress().toString()
|
|
1133
|
+
}
|
|
1134
|
+
});
|
|
456
1135
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
457
1136
|
}
|
|
458
1137
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -472,17 +1151,27 @@ export class SequencerPublisher {
|
|
|
472
1151
|
payload: payload.toString()
|
|
473
1152
|
};
|
|
474
1153
|
if (!success) {
|
|
475
|
-
this.log.error(`Signaling in
|
|
1154
|
+
this.log.error(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
|
|
476
1155
|
this.lastActions[signalType] = cachedLastVote;
|
|
477
1156
|
return false;
|
|
478
1157
|
} else {
|
|
479
|
-
this.log.info(`Signaling in
|
|
1158
|
+
this.log.info(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
|
|
480
1159
|
return true;
|
|
481
1160
|
}
|
|
482
1161
|
}
|
|
483
1162
|
});
|
|
484
1163
|
return true;
|
|
485
1164
|
}
|
|
1165
|
+
async isPayloadEmpty(payload) {
|
|
1166
|
+
const key = payload.toString();
|
|
1167
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
1168
|
+
if (cached) {
|
|
1169
|
+
return cached;
|
|
1170
|
+
}
|
|
1171
|
+
const isEmpty = !await this.l1TxUtils.getCode(payload);
|
|
1172
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
1173
|
+
return isEmpty;
|
|
1174
|
+
}
|
|
486
1175
|
/**
|
|
487
1176
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
488
1177
|
* @param slotNumber - The slot number to cast a signal for.
|
|
@@ -578,22 +1267,17 @@ export class SequencerPublisher {
|
|
|
578
1267
|
}
|
|
579
1268
|
return true;
|
|
580
1269
|
}
|
|
581
|
-
/**
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
* @returns True if the tx has been enqueued, throws otherwise. See #9315
|
|
586
|
-
*/ async enqueueProposeL2Block(block, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
587
|
-
const checkpointHeader = block.getCheckpointHeader();
|
|
588
|
-
const blobFields = block.getCheckpointBlobFields();
|
|
589
|
-
const blobs = getBlobsPerL1Block(blobFields);
|
|
1270
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1271
|
+
const checkpointHeader = checkpoint.header;
|
|
1272
|
+
const blobFields = checkpoint.toBlobFields();
|
|
1273
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
590
1274
|
const proposeTxArgs = {
|
|
591
1275
|
header: checkpointHeader,
|
|
592
|
-
archive:
|
|
593
|
-
body: block.body.toBuffer(),
|
|
1276
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
594
1277
|
blobs,
|
|
595
1278
|
attestationsAndSigners,
|
|
596
|
-
attestationsAndSignersSignature
|
|
1279
|
+
attestationsAndSignersSignature,
|
|
1280
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
597
1281
|
};
|
|
598
1282
|
let ts;
|
|
599
1283
|
try {
|
|
@@ -602,36 +1286,35 @@ export class SequencerPublisher {
|
|
|
602
1286
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
603
1287
|
// make time consistency checks break.
|
|
604
1288
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
605
|
-
ts = await this.
|
|
1289
|
+
ts = await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts);
|
|
606
1290
|
} catch (err) {
|
|
607
|
-
this.log.error(`
|
|
608
|
-
...
|
|
609
|
-
slotNumber:
|
|
610
|
-
|
|
1291
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1292
|
+
...checkpoint.getStats(),
|
|
1293
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
1294
|
+
forcePendingCheckpointNumber: opts.forcePendingCheckpointNumber
|
|
611
1295
|
});
|
|
612
1296
|
throw err;
|
|
613
1297
|
}
|
|
614
|
-
this.log.verbose(`Enqueuing
|
|
615
|
-
...
|
|
1298
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1299
|
+
...checkpoint.toCheckpointInfo(),
|
|
616
1300
|
...opts
|
|
617
1301
|
});
|
|
618
|
-
await this.addProposeTx(
|
|
619
|
-
return true;
|
|
1302
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, opts, ts);
|
|
620
1303
|
}
|
|
621
|
-
|
|
1304
|
+
enqueueInvalidateCheckpoint(request, opts = {}) {
|
|
622
1305
|
if (!request) {
|
|
623
1306
|
return;
|
|
624
1307
|
}
|
|
625
1308
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
626
1309
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(request.gasUsed) * 64 / 63)));
|
|
627
|
-
const { gasUsed,
|
|
1310
|
+
const { gasUsed, checkpointNumber } = request;
|
|
628
1311
|
const logData = {
|
|
629
1312
|
gasUsed,
|
|
630
|
-
|
|
1313
|
+
checkpointNumber,
|
|
631
1314
|
gasLimit,
|
|
632
1315
|
opts
|
|
633
1316
|
};
|
|
634
|
-
this.log.verbose(`Enqueuing invalidate
|
|
1317
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
635
1318
|
this.addRequest({
|
|
636
1319
|
action: `invalidate-by-${request.reason}`,
|
|
637
1320
|
request: request.request,
|
|
@@ -643,12 +1326,12 @@ export class SequencerPublisher {
|
|
|
643
1326
|
checkSuccess: (_req, result)=>{
|
|
644
1327
|
const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
645
1328
|
if (!success) {
|
|
646
|
-
this.log.warn(`Invalidate
|
|
1329
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, {
|
|
647
1330
|
...result,
|
|
648
1331
|
...logData
|
|
649
1332
|
});
|
|
650
1333
|
} else {
|
|
651
|
-
this.log.info(`Invalidate
|
|
1334
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, {
|
|
652
1335
|
...result,
|
|
653
1336
|
...logData
|
|
654
1337
|
});
|
|
@@ -670,28 +1353,60 @@ export class SequencerPublisher {
|
|
|
670
1353
|
const cachedLastActionSlot = this.lastActions[action];
|
|
671
1354
|
this.lastActions[action] = slotNumber;
|
|
672
1355
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1356
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
673
1357
|
let gasUsed;
|
|
1358
|
+
const simulateAbi = mergeAbis([
|
|
1359
|
+
request.abi ?? [],
|
|
1360
|
+
ErrorsAbi
|
|
1361
|
+
]);
|
|
674
1362
|
try {
|
|
675
1363
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
676
1364
|
time: timestamp
|
|
677
|
-
}, [],
|
|
1365
|
+
}, [], simulateAbi)); // TODO(palla/slash): Check the timestamp logic
|
|
678
1366
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
679
1367
|
...logData,
|
|
680
1368
|
request,
|
|
681
1369
|
gasUsed
|
|
682
1370
|
});
|
|
683
1371
|
} catch (err) {
|
|
684
|
-
const viemError = formatViemError(err);
|
|
1372
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
685
1373
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1374
|
+
this.backupFailedTx({
|
|
1375
|
+
id: keccak256(request.data),
|
|
1376
|
+
failureType: 'simulation',
|
|
1377
|
+
request: {
|
|
1378
|
+
to: request.to,
|
|
1379
|
+
data: request.data,
|
|
1380
|
+
value: request.value?.toString()
|
|
1381
|
+
},
|
|
1382
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1383
|
+
error: {
|
|
1384
|
+
message: viemError.message,
|
|
1385
|
+
name: viemError.name
|
|
1386
|
+
},
|
|
1387
|
+
context: {
|
|
1388
|
+
actions: [
|
|
1389
|
+
action
|
|
1390
|
+
],
|
|
1391
|
+
slot: slotNumber,
|
|
1392
|
+
sender: this.getSenderAddress().toString()
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
686
1395
|
return false;
|
|
687
1396
|
}
|
|
688
1397
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
689
1398
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
|
|
690
1399
|
logData.gasLimit = gasLimit;
|
|
1400
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1401
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1402
|
+
const requestWithAbi = {
|
|
1403
|
+
...request,
|
|
1404
|
+
abi: simulateAbi
|
|
1405
|
+
};
|
|
691
1406
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
692
1407
|
this.addRequest({
|
|
693
1408
|
action,
|
|
694
|
-
request,
|
|
1409
|
+
request: requestWithAbi,
|
|
695
1410
|
gasConfig: {
|
|
696
1411
|
gasLimit
|
|
697
1412
|
},
|
|
@@ -755,10 +1470,38 @@ export class SequencerPublisher {
|
|
|
755
1470
|
}, {}, {
|
|
756
1471
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
757
1472
|
kzg
|
|
758
|
-
}).catch((err)=>{
|
|
759
|
-
const
|
|
760
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
761
|
-
metaMessages
|
|
1473
|
+
}).catch(async (err)=>{
|
|
1474
|
+
const viemError = formatViemError(err);
|
|
1475
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1476
|
+
metaMessages: viemError.metaMessages
|
|
1477
|
+
});
|
|
1478
|
+
const validateBlobsData = encodeFunctionData({
|
|
1479
|
+
abi: RollupAbi,
|
|
1480
|
+
functionName: 'validateBlobs',
|
|
1481
|
+
args: [
|
|
1482
|
+
blobInput
|
|
1483
|
+
]
|
|
1484
|
+
});
|
|
1485
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1486
|
+
this.backupFailedTx({
|
|
1487
|
+
id: keccak256(validateBlobsData),
|
|
1488
|
+
failureType: 'simulation',
|
|
1489
|
+
request: {
|
|
1490
|
+
to: this.rollupContract.address,
|
|
1491
|
+
data: validateBlobsData
|
|
1492
|
+
},
|
|
1493
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1494
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1495
|
+
error: {
|
|
1496
|
+
message: viemError.message,
|
|
1497
|
+
name: viemError.name
|
|
1498
|
+
},
|
|
1499
|
+
context: {
|
|
1500
|
+
actions: [
|
|
1501
|
+
'validate-blobs'
|
|
1502
|
+
],
|
|
1503
|
+
sender: this.getSenderAddress().toString()
|
|
1504
|
+
}
|
|
762
1505
|
});
|
|
763
1506
|
throw new Error('Failed to validate blobs');
|
|
764
1507
|
});
|
|
@@ -769,8 +1512,7 @@ export class SequencerPublisher {
|
|
|
769
1512
|
header: encodedData.header.toViem(),
|
|
770
1513
|
archive: toHex(encodedData.archive),
|
|
771
1514
|
oracleInput: {
|
|
772
|
-
|
|
773
|
-
feeAssetPriceModifier: 0n
|
|
1515
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
774
1516
|
}
|
|
775
1517
|
},
|
|
776
1518
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -797,8 +1539,8 @@ export class SequencerPublisher {
|
|
|
797
1539
|
functionName: 'propose',
|
|
798
1540
|
args
|
|
799
1541
|
});
|
|
800
|
-
// override the pending
|
|
801
|
-
const
|
|
1542
|
+
// override the pending checkpoint number if requested
|
|
1543
|
+
const forcePendingCheckpointNumberStateDiff = (options.forcePendingCheckpointNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingCheckpointNumber) : []).flatMap((override)=>override.stateDiff ?? []);
|
|
802
1544
|
const stateOverrides = [
|
|
803
1545
|
{
|
|
804
1546
|
address: this.rollupContract.address,
|
|
@@ -808,7 +1550,7 @@ export class SequencerPublisher {
|
|
|
808
1550
|
slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
|
|
809
1551
|
value: toPaddedHex(0n, true)
|
|
810
1552
|
},
|
|
811
|
-
...
|
|
1553
|
+
...forcePendingCheckpointNumberStateDiff
|
|
812
1554
|
]
|
|
813
1555
|
}
|
|
814
1556
|
];
|
|
@@ -819,10 +1561,11 @@ export class SequencerPublisher {
|
|
|
819
1561
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
820
1562
|
});
|
|
821
1563
|
}
|
|
1564
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
822
1565
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
823
1566
|
to: this.rollupContract.address,
|
|
824
1567
|
data: rollupData,
|
|
825
|
-
gas:
|
|
1568
|
+
gas: MAX_L1_TX_LIMIT,
|
|
826
1569
|
...this.proposerAddressForSimulation && {
|
|
827
1570
|
from: this.proposerAddressForSimulation.toString()
|
|
828
1571
|
}
|
|
@@ -830,10 +1573,10 @@ export class SequencerPublisher {
|
|
|
830
1573
|
// @note we add 1n to the timestamp because geth implementation doesn't like simulation timestamp to be equal to the current block timestamp
|
|
831
1574
|
time: timestamp + 1n,
|
|
832
1575
|
// @note reth should have a 30m gas limit per block but throws errors that this tx is beyond limit so we increase here
|
|
833
|
-
gasLimit:
|
|
1576
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
834
1577
|
}, stateOverrides, RollupAbi, {
|
|
835
1578
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
836
|
-
fallbackGasEstimate:
|
|
1579
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT
|
|
837
1580
|
}).catch((err)=>{
|
|
838
1581
|
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
839
1582
|
const viemError = formatViemError(err);
|
|
@@ -841,11 +1584,31 @@ export class SequencerPublisher {
|
|
|
841
1584
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
842
1585
|
// Return a minimal simulation result with the fallback gas estimate
|
|
843
1586
|
return {
|
|
844
|
-
gasUsed:
|
|
1587
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
845
1588
|
logs: []
|
|
846
1589
|
};
|
|
847
1590
|
}
|
|
848
1591
|
this.log.error(`Failed to simulate propose tx`, viemError);
|
|
1592
|
+
this.backupFailedTx({
|
|
1593
|
+
id: keccak256(rollupData),
|
|
1594
|
+
failureType: 'simulation',
|
|
1595
|
+
request: {
|
|
1596
|
+
to: this.rollupContract.address,
|
|
1597
|
+
data: rollupData
|
|
1598
|
+
},
|
|
1599
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1600
|
+
error: {
|
|
1601
|
+
message: viemError.message,
|
|
1602
|
+
name: viemError.name
|
|
1603
|
+
},
|
|
1604
|
+
context: {
|
|
1605
|
+
actions: [
|
|
1606
|
+
'propose'
|
|
1607
|
+
],
|
|
1608
|
+
slot: Number(args[0].header.slotNumber),
|
|
1609
|
+
sender: this.getSenderAddress().toString()
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
849
1612
|
throw err;
|
|
850
1613
|
});
|
|
851
1614
|
return {
|
|
@@ -853,24 +1616,25 @@ export class SequencerPublisher {
|
|
|
853
1616
|
simulationResult
|
|
854
1617
|
};
|
|
855
1618
|
}
|
|
856
|
-
async addProposeTx(
|
|
1619
|
+
async addProposeTx(checkpoint, encodedData, opts = {}, timestamp) {
|
|
1620
|
+
const slot = checkpoint.header.slotNumber;
|
|
857
1621
|
const timer = new Timer();
|
|
858
1622
|
const kzg = Blob.getViemKzgInstance();
|
|
859
1623
|
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, timestamp, opts);
|
|
860
1624
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
861
1625
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(simulationResult.gasUsed) * 64 / 63)) + blobEvaluationGas + SequencerPublisher.MULTICALL_OVERHEAD_GAS_GUESS);
|
|
862
|
-
// Send the blobs to the blob
|
|
863
|
-
// tx fails but it does get mined. We make sure that the blobs are sent to the blob
|
|
864
|
-
void this.
|
|
865
|
-
|
|
866
|
-
|
|
1626
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1627
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1628
|
+
void Promise.resolve().then(()=>this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch((_err)=>{
|
|
1629
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1630
|
+
}));
|
|
867
1631
|
return this.addRequest({
|
|
868
1632
|
action: 'propose',
|
|
869
1633
|
request: {
|
|
870
1634
|
to: this.rollupContract.address,
|
|
871
1635
|
data: rollupData
|
|
872
1636
|
},
|
|
873
|
-
lastValidL2Slot:
|
|
1637
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
874
1638
|
gasConfig: {
|
|
875
1639
|
...opts,
|
|
876
1640
|
gasLimit
|
|
@@ -898,29 +1662,42 @@ export class SequencerPublisher {
|
|
|
898
1662
|
calldataGas,
|
|
899
1663
|
calldataSize,
|
|
900
1664
|
sender,
|
|
901
|
-
...
|
|
1665
|
+
...checkpoint.getStats(),
|
|
902
1666
|
eventName: 'rollup-published-to-l1',
|
|
903
1667
|
blobCount: encodedData.blobs.length,
|
|
904
1668
|
inclusionBlocks
|
|
905
1669
|
};
|
|
906
|
-
this.log.info(`Published
|
|
1670
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
907
1671
|
...stats,
|
|
908
|
-
...
|
|
909
|
-
...receipt
|
|
1672
|
+
...checkpoint.getStats(),
|
|
1673
|
+
...pick(receipt, 'transactionHash', 'blockHash')
|
|
910
1674
|
});
|
|
911
1675
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
912
1676
|
return true;
|
|
913
1677
|
} else {
|
|
914
1678
|
this.metrics.recordFailedTx('process');
|
|
915
|
-
this.log.error(`
|
|
916
|
-
...
|
|
917
|
-
receipt
|
|
918
|
-
txHash: receipt.transactionHash,
|
|
919
|
-
slotNumber: block.header.globalVariables.slotNumber
|
|
1679
|
+
this.log.error(`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`, undefined, {
|
|
1680
|
+
...checkpoint.getStats(),
|
|
1681
|
+
...receipt
|
|
920
1682
|
});
|
|
921
1683
|
return false;
|
|
922
1684
|
}
|
|
923
1685
|
}
|
|
924
1686
|
});
|
|
925
1687
|
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Returns the timestamp to use when simulating L1 proposal calls.
|
|
1690
|
+
* Uses the wall-clock-based next L1 slot boundary, but floors it with the latest L1 block timestamp
|
|
1691
|
+
* plus one slot duration. This prevents the sequencer from targeting a future L2 slot when the L1
|
|
1692
|
+
* chain hasn't caught up to the wall clock yet (e.g., the dateProvider is one L1 slot ahead of the
|
|
1693
|
+
* latest mined block), which would cause the propose tx to land in an L1 block with block.timestamp
|
|
1694
|
+
* still in the previous L2 slot.
|
|
1695
|
+
* TODO(palla): Properly fix by keeping dateProvider synced with anvil's chain time on every block.
|
|
1696
|
+
*/ async getNextL1SlotTimestampWithL1Floor() {
|
|
1697
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1698
|
+
const fromWallClock = getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1699
|
+
const latestBlock = await this.l1TxUtils.client.getBlock();
|
|
1700
|
+
const fromL1Block = latestBlock.timestamp + BigInt(l1Constants.ethereumSlotDuration);
|
|
1701
|
+
return fromWallClock > fromL1Block ? fromWallClock : fromL1Block;
|
|
1702
|
+
}
|
|
926
1703
|
}
|