@aztec/sequencer-client 0.0.1-commit.5daedc8 → 0.0.1-commit.6201a7b05
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 +26 -16
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +76 -36
- package/dest/config.d.ts +35 -9
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +113 -50
- package/dest/global_variable_builder/fee_predictor.d.ts +37 -0
- package/dest/global_variable_builder/fee_predictor.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_predictor.js +128 -0
- package/dest/global_variable_builder/fee_provider.d.ts +21 -0
- package/dest/global_variable_builder/fee_provider.d.ts.map +1 -0
- package/dest/global_variable_builder/fee_provider.js +58 -0
- package/dest/global_variable_builder/global_builder.d.ts +25 -17
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +44 -68
- package/dest/global_variable_builder/index.d.ts +4 -2
- package/dest/global_variable_builder/index.d.ts.map +1 -1
- package/dest/global_variable_builder/index.js +2 -0
- 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 +58 -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 -8
- package/dest/publisher/sequencer-publisher-factory.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher-factory.js +28 -4
- 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 +114 -80
- package/dest/publisher/sequencer-publisher.d.ts.map +1 -1
- package/dest/publisher/sequencer-publisher.js +1010 -244
- package/dest/sequencer/chain_state_overrides.d.ts +25 -0
- package/dest/sequencer/chain_state_overrides.d.ts.map +1 -0
- package/dest/sequencer/chain_state_overrides.js +39 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts +138 -0
- package/dest/sequencer/checkpoint_proposal_job.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job.js +1665 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts +34 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_proposal_job_metrics.js +72 -0
- package/dest/sequencer/checkpoint_voter.d.ts +34 -0
- package/dest/sequencer/checkpoint_voter.d.ts.map +1 -0
- package/dest/sequencer/checkpoint_voter.js +106 -0
- package/dest/sequencer/config.d.ts +3 -2
- package/dest/sequencer/config.d.ts.map +1 -1
- package/dest/sequencer/events.d.ts +52 -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 -7
- package/dest/sequencer/metrics.d.ts.map +1 -1
- package/dest/sequencer/metrics.js +241 -72
- package/dest/sequencer/sequencer.d.ts +146 -135
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +841 -661
- package/dest/sequencer/timetable.d.ts +67 -16
- package/dest/sequencer/timetable.d.ts.map +1 -1
- package/dest/sequencer/timetable.js +158 -64
- 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 +105 -0
- package/package.json +31 -30
- package/src/client/sequencer-client.ts +102 -62
- package/src/config.ts +132 -59
- package/src/global_variable_builder/README.md +44 -0
- package/src/global_variable_builder/fee_predictor.ts +172 -0
- package/src/global_variable_builder/fee_provider.ts +75 -0
- package/src/global_variable_builder/global_builder.ts +64 -96
- package/src/global_variable_builder/index.ts +3 -1
- package/src/index.ts +1 -7
- package/src/publisher/config.ts +166 -51
- 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 +57 -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 -13
- package/src/publisher/sequencer-publisher-metrics.ts +19 -71
- package/src/publisher/sequencer-publisher.ts +763 -325
- package/src/sequencer/README.md +600 -0
- package/src/sequencer/chain_state_overrides.ts +87 -0
- package/src/sequencer/checkpoint_proposal_job.ts +1498 -0
- package/src/sequencer/checkpoint_proposal_job_metrics.ts +128 -0
- package/src/sequencer/checkpoint_voter.ts +119 -0
- package/src/sequencer/config.ts +2 -1
- package/src/sequencer/events.ts +32 -0
- package/src/sequencer/index.ts +3 -1
- package/src/sequencer/metrics.ts +302 -83
- package/src/sequencer/sequencer.ts +641 -863
- package/src/sequencer/timetable.ts +188 -81
- 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 +185 -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,49 +1,452 @@
|
|
|
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, SimulationOverridesBuilder, buildSimulationOverridesStateOverride } 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
|
-
import {
|
|
6
|
-
import {
|
|
380
|
+
import { CheckpointNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
381
|
+
import { trimmedBytesLength } from '@aztec/foundation/buffer';
|
|
382
|
+
import { pick } from '@aztec/foundation/collection';
|
|
383
|
+
import { TimeoutError } from '@aztec/foundation/error';
|
|
7
384
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
8
385
|
import { Signature } from '@aztec/foundation/eth-signature';
|
|
9
386
|
import { createLogger } from '@aztec/foundation/log';
|
|
387
|
+
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
388
|
+
import { InterruptibleSleep } from '@aztec/foundation/sleep';
|
|
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 { getLastL1SlotTimestampForL2Slot, 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',
|
|
20
401
|
'invalidate-by-insufficient-attestations',
|
|
21
402
|
'propose',
|
|
22
403
|
'governance-signal',
|
|
23
|
-
'empire-slashing-signal',
|
|
24
|
-
'create-empire-payload',
|
|
25
|
-
'execute-empire-payload',
|
|
26
404
|
'vote-offenses',
|
|
27
405
|
'execute-slash'
|
|
28
406
|
];
|
|
29
407
|
// Sorting for actions such that invalidations go before proposals, and proposals go before votes
|
|
30
408
|
export const compareActions = (a, b)=>Actions.indexOf(a) - Actions.indexOf(b);
|
|
409
|
+
_dec = trackSpan('SequencerPublisher.sendRequests'), _dec1 = trackSpan('SequencerPublisher.validateBlockHeader'), _dec2 = trackSpan('SequencerPublisher.validateCheckpointForSubmission');
|
|
31
410
|
export class SequencerPublisher {
|
|
32
411
|
config;
|
|
412
|
+
static{
|
|
413
|
+
({ e: [_initProto] } = _apply_decs_2203_r(this, [
|
|
414
|
+
[
|
|
415
|
+
_dec,
|
|
416
|
+
2,
|
|
417
|
+
"sendRequests"
|
|
418
|
+
],
|
|
419
|
+
[
|
|
420
|
+
_dec1,
|
|
421
|
+
2,
|
|
422
|
+
"validateBlockHeader"
|
|
423
|
+
],
|
|
424
|
+
[
|
|
425
|
+
_dec2,
|
|
426
|
+
2,
|
|
427
|
+
"validateCheckpointForSubmission"
|
|
428
|
+
]
|
|
429
|
+
], []));
|
|
430
|
+
}
|
|
33
431
|
interrupted;
|
|
34
432
|
metrics;
|
|
35
433
|
epochCache;
|
|
434
|
+
failedTxStore;
|
|
36
435
|
governanceLog;
|
|
37
436
|
slashingLog;
|
|
38
437
|
lastActions;
|
|
438
|
+
isPayloadEmptyCache;
|
|
439
|
+
payloadProposedCache;
|
|
39
440
|
log;
|
|
40
441
|
ethereumSlotDuration;
|
|
41
|
-
|
|
442
|
+
aztecSlotDuration;
|
|
443
|
+
/** Date provider for wall-clock time. */ dateProvider;
|
|
444
|
+
blobClient;
|
|
42
445
|
/** Address to use for simulations in fisherman mode (actual proposer's address) */ proposerAddressForSimulation;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
446
|
+
/** Optional callback to obtain a replacement publisher when the current one fails to send. */ getNextPublisher;
|
|
447
|
+
/** L1 fee analyzer for fisherman mode */ l1FeeAnalyzer;
|
|
448
|
+
/** Fee asset price oracle for computing price modifiers from Uniswap V4 */ feeAssetPriceOracle;
|
|
449
|
+
/** Interruptible sleep used by sendRequestsAt to wait until a target timestamp. */ interruptibleSleep;
|
|
47
450
|
// A CALL to a cold address is 2700 gas
|
|
48
451
|
static MULTICALL_OVERHEAD_GAS_GUESS = 5000n;
|
|
49
452
|
// Gas report for VotingWithSigTest shows a max gas of 100k, but we've seen it cost 700k+ in testnet
|
|
@@ -52,25 +455,31 @@ export class SequencerPublisher {
|
|
|
52
455
|
rollupContract;
|
|
53
456
|
govProposerContract;
|
|
54
457
|
slashingProposerContract;
|
|
55
|
-
|
|
458
|
+
tracer;
|
|
56
459
|
requests;
|
|
57
460
|
constructor(config, deps){
|
|
58
461
|
this.config = config;
|
|
59
|
-
this.interrupted = false;
|
|
462
|
+
this.interrupted = (_initProto(this), false);
|
|
60
463
|
this.governanceLog = createLogger('sequencer:publisher:governance');
|
|
61
464
|
this.slashingLog = createLogger('sequencer:publisher:slashing');
|
|
62
465
|
this.lastActions = {};
|
|
466
|
+
this.isPayloadEmptyCache = new Map();
|
|
467
|
+
this.payloadProposedCache = new Set();
|
|
468
|
+
this.interruptibleSleep = new InterruptibleSleep();
|
|
63
469
|
this.requests = [];
|
|
64
470
|
this.log = deps.log ?? createLogger('sequencer:publisher');
|
|
65
471
|
this.ethereumSlotDuration = BigInt(config.ethereumSlotDuration);
|
|
472
|
+
this.aztecSlotDuration = BigInt(config.aztecSlotDuration);
|
|
473
|
+
this.dateProvider = deps.dateProvider;
|
|
66
474
|
this.epochCache = deps.epochCache;
|
|
67
475
|
this.lastActions = deps.lastActions;
|
|
68
|
-
this.
|
|
69
|
-
|
|
70
|
-
});
|
|
476
|
+
this.blobClient = deps.blobClient;
|
|
477
|
+
this.dateProvider = deps.dateProvider;
|
|
71
478
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
72
479
|
this.metrics = deps.metrics ?? new SequencerPublisherMetrics(telemetry, 'SequencerPublisher');
|
|
480
|
+
this.tracer = telemetry.getTracer('SequencerPublisher');
|
|
73
481
|
this.l1TxUtils = deps.l1TxUtils;
|
|
482
|
+
this.getNextPublisher = deps.getNextPublisher;
|
|
74
483
|
this.rollupContract = deps.rollupContract;
|
|
75
484
|
this.govProposerContract = deps.governanceProposerContract;
|
|
76
485
|
this.slashingProposerContract = deps.slashingProposerContract;
|
|
@@ -79,15 +488,49 @@ export class SequencerPublisher {
|
|
|
79
488
|
const newSlashingProposer = await this.rollupContract.getSlashingProposer();
|
|
80
489
|
this.slashingProposerContract = newSlashingProposer;
|
|
81
490
|
});
|
|
82
|
-
|
|
491
|
+
// Initialize L1 fee analyzer for fisherman mode
|
|
492
|
+
if (config.fishermanMode) {
|
|
493
|
+
this.l1FeeAnalyzer = new L1FeeAnalyzer(this.l1TxUtils.client, deps.dateProvider, createLogger('sequencer:publisher:fee-analyzer'));
|
|
494
|
+
}
|
|
495
|
+
// Initialize fee asset price oracle
|
|
496
|
+
this.feeAssetPriceOracle = new FeeAssetPriceOracle(this.l1TxUtils.client, this.rollupContract, createLogger('sequencer:publisher:price-oracle'));
|
|
497
|
+
// Initialize failed L1 tx store (optional, for test networks)
|
|
498
|
+
this.failedTxStore = createL1TxFailedStore(config.l1TxFailedStore, this.log);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Backs up a failed L1 transaction to the configured store for debugging.
|
|
502
|
+
* Does nothing if no store is configured.
|
|
503
|
+
*/ backupFailedTx(failedTx) {
|
|
504
|
+
if (!this.failedTxStore) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
const tx = {
|
|
508
|
+
...failedTx,
|
|
509
|
+
timestamp: Date.now()
|
|
510
|
+
};
|
|
511
|
+
// Fire and forget - don't block on backup
|
|
512
|
+
void this.failedTxStore.then((store)=>store?.saveFailedTx(tx)).catch((err)=>{
|
|
513
|
+
this.log.warn(`Failed to backup failed L1 tx to store`, err);
|
|
514
|
+
});
|
|
83
515
|
}
|
|
84
516
|
getRollupContract() {
|
|
85
517
|
return this.rollupContract;
|
|
86
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Gets the fee asset price modifier from the oracle.
|
|
521
|
+
* Returns 0n if the oracle query fails.
|
|
522
|
+
*/ getFeeAssetPriceModifier() {
|
|
523
|
+
return this.feeAssetPriceOracle.computePriceModifier();
|
|
524
|
+
}
|
|
87
525
|
getSenderAddress() {
|
|
88
526
|
return this.l1TxUtils.getSenderAddress();
|
|
89
527
|
}
|
|
90
528
|
/**
|
|
529
|
+
* Gets the L1 fee analyzer instance (only available in fisherman mode)
|
|
530
|
+
*/ getL1FeeAnalyzer() {
|
|
531
|
+
return this.l1FeeAnalyzer;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
91
534
|
* Sets the proposer address to use for simulations in fisherman mode.
|
|
92
535
|
* @param proposerAddress - The actual proposer's address to use for balance lookups in simulations
|
|
93
536
|
*/ setProposerAddressForSimulation(proposerAddress) {
|
|
@@ -97,7 +540,7 @@ export class SequencerPublisher {
|
|
|
97
540
|
this.requests.push(request);
|
|
98
541
|
}
|
|
99
542
|
getCurrentL2Slot() {
|
|
100
|
-
return this.epochCache.
|
|
543
|
+
return this.epochCache.getSlotNow();
|
|
101
544
|
}
|
|
102
545
|
/**
|
|
103
546
|
* Clears all pending requests without sending them.
|
|
@@ -109,6 +552,46 @@ export class SequencerPublisher {
|
|
|
109
552
|
}
|
|
110
553
|
}
|
|
111
554
|
/**
|
|
555
|
+
* Analyzes L1 fees for the pending requests without sending them.
|
|
556
|
+
* This is used in fisherman mode to validate fee calculations.
|
|
557
|
+
* @param l2SlotNumber - The L2 slot number for this analysis
|
|
558
|
+
* @param onComplete - Optional callback to invoke when analysis completes (after block is mined)
|
|
559
|
+
* @returns The analysis result (incomplete until block mines), or undefined if no requests
|
|
560
|
+
*/ async analyzeL1Fees(l2SlotNumber, onComplete) {
|
|
561
|
+
if (!this.l1FeeAnalyzer) {
|
|
562
|
+
this.log.warn('L1 fee analyzer not available (not in fisherman mode)');
|
|
563
|
+
return undefined;
|
|
564
|
+
}
|
|
565
|
+
const requestsToAnalyze = [
|
|
566
|
+
...this.requests
|
|
567
|
+
];
|
|
568
|
+
if (requestsToAnalyze.length === 0) {
|
|
569
|
+
this.log.debug('No requests to analyze for L1 fees');
|
|
570
|
+
return undefined;
|
|
571
|
+
}
|
|
572
|
+
// Extract blob config from requests (if any)
|
|
573
|
+
const blobConfigs = requestsToAnalyze.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
|
|
574
|
+
const blobConfig = blobConfigs[0];
|
|
575
|
+
// Get gas configs
|
|
576
|
+
const gasConfigs = requestsToAnalyze.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
|
|
577
|
+
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
578
|
+
const gasLimit = gasLimits.length > 0 ? gasLimits.reduce((sum, g)=>sum + g, 0n) : 0n;
|
|
579
|
+
// Get the transaction requests
|
|
580
|
+
const l1Requests = requestsToAnalyze.map((r)=>r.request);
|
|
581
|
+
// Start the analysis
|
|
582
|
+
const analysisId = await this.l1FeeAnalyzer.startAnalysis(l2SlotNumber, gasLimit > 0n ? gasLimit : MAX_L1_TX_LIMIT, l1Requests, blobConfig, onComplete);
|
|
583
|
+
this.log.info('Started L1 fee analysis', {
|
|
584
|
+
analysisId,
|
|
585
|
+
l2SlotNumber: l2SlotNumber.toString(),
|
|
586
|
+
requestCount: requestsToAnalyze.length,
|
|
587
|
+
hasBlobConfig: !!blobConfig,
|
|
588
|
+
gasLimit: gasLimit.toString(),
|
|
589
|
+
actions: requestsToAnalyze.map((r)=>r.action)
|
|
590
|
+
});
|
|
591
|
+
// Return the analysis result (will be incomplete until block mines)
|
|
592
|
+
return this.l1FeeAnalyzer.getAnalysis(analysisId);
|
|
593
|
+
}
|
|
594
|
+
/**
|
|
112
595
|
* Sends all requests that are still valid.
|
|
113
596
|
* @returns one of:
|
|
114
597
|
* - A receipt and stats if the tx succeeded
|
|
@@ -119,7 +602,7 @@ export class SequencerPublisher {
|
|
|
119
602
|
...this.requests
|
|
120
603
|
];
|
|
121
604
|
this.requests = [];
|
|
122
|
-
if (this.interrupted) {
|
|
605
|
+
if (this.interrupted || requestsToProcess.length === 0) {
|
|
123
606
|
return undefined;
|
|
124
607
|
}
|
|
125
608
|
const currentL2Slot = this.getCurrentL2Slot();
|
|
@@ -146,15 +629,24 @@ export class SequencerPublisher {
|
|
|
146
629
|
// @note - we can only have one blob config per bundle
|
|
147
630
|
// find requests with gas and blob configs
|
|
148
631
|
// See https://github.com/AztecProtocol/aztec-packages/issues/11513
|
|
149
|
-
const gasConfigs =
|
|
150
|
-
const blobConfigs =
|
|
632
|
+
const gasConfigs = validRequests.filter((request)=>request.gasConfig).map((request)=>request.gasConfig);
|
|
633
|
+
const blobConfigs = validRequests.filter((request)=>request.blobConfig).map((request)=>request.blobConfig);
|
|
151
634
|
if (blobConfigs.length > 1) {
|
|
152
635
|
throw new Error('Multiple blob configs found');
|
|
153
636
|
}
|
|
154
637
|
const blobConfig = blobConfigs[0];
|
|
155
638
|
// Merge gasConfigs. Yields the sum of gasLimits, and the earliest txTimeoutAt, or undefined if no gasConfig sets them.
|
|
156
639
|
const gasLimits = gasConfigs.map((g)=>g?.gasLimit).filter((g)=>g !== undefined);
|
|
157
|
-
|
|
640
|
+
let gasLimit = gasLimits.length > 0 ? sumBigint(gasLimits) : undefined; // sum
|
|
641
|
+
// Cap at L1 block gas limit so the node accepts the tx ("gas limit too high" otherwise).
|
|
642
|
+
const maxGas = MAX_L1_TX_LIMIT;
|
|
643
|
+
if (gasLimit !== undefined && gasLimit > maxGas) {
|
|
644
|
+
this.log.debug('Capping bundled tx gas limit to L1 max', {
|
|
645
|
+
requested: gasLimit,
|
|
646
|
+
capped: maxGas
|
|
647
|
+
});
|
|
648
|
+
gasLimit = maxGas;
|
|
649
|
+
}
|
|
158
650
|
const txTimeoutAts = gasConfigs.map((g)=>g?.txTimeoutAt).filter((g)=>g !== undefined);
|
|
159
651
|
const txTimeoutAt = txTimeoutAts.length > 0 ? new Date(Math.min(...txTimeoutAts.map((g)=>g.getTime()))) : undefined; // earliest
|
|
160
652
|
const txConfig = {
|
|
@@ -165,12 +657,34 @@ export class SequencerPublisher {
|
|
|
165
657
|
// This ensures the committee gets precomputed correctly
|
|
166
658
|
validRequests.sort((a, b)=>compareActions(a.action, b.action));
|
|
167
659
|
try {
|
|
660
|
+
// Capture context for failed tx backup before sending
|
|
661
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
662
|
+
const multicallData = encodeFunctionData({
|
|
663
|
+
abi: multicall3Abi,
|
|
664
|
+
functionName: 'aggregate3',
|
|
665
|
+
args: [
|
|
666
|
+
validRequests.map((r)=>({
|
|
667
|
+
target: r.request.to,
|
|
668
|
+
callData: r.request.data,
|
|
669
|
+
allowFailure: true
|
|
670
|
+
}))
|
|
671
|
+
]
|
|
672
|
+
});
|
|
673
|
+
const blobDataHex = blobConfig?.blobs?.map((b)=>toHex(b));
|
|
674
|
+
const txContext = {
|
|
675
|
+
multicallData,
|
|
676
|
+
blobData: blobDataHex,
|
|
677
|
+
l1BlockNumber
|
|
678
|
+
};
|
|
168
679
|
this.log.debug('Forwarding transactions', {
|
|
169
680
|
validRequests: validRequests.map((request)=>request.action),
|
|
170
681
|
txConfig
|
|
171
682
|
});
|
|
172
|
-
const result = await
|
|
173
|
-
|
|
683
|
+
const result = await this.forwardWithPublisherRotation(validRequests, txConfig, blobConfig);
|
|
684
|
+
if (result === undefined) {
|
|
685
|
+
return undefined;
|
|
686
|
+
}
|
|
687
|
+
const { successfulActions = [], failedActions = [] } = this.callbackBundledTransactions(validRequests, result, txContext);
|
|
174
688
|
return {
|
|
175
689
|
result,
|
|
176
690
|
expiredActions,
|
|
@@ -190,17 +704,118 @@ export class SequencerPublisher {
|
|
|
190
704
|
}
|
|
191
705
|
}
|
|
192
706
|
}
|
|
193
|
-
|
|
707
|
+
/**
|
|
708
|
+
* Forwards transactions via Multicall3, rotating to the next available publisher if a send
|
|
709
|
+
* failure occurs (i.e. the tx never reached the chain).
|
|
710
|
+
* On-chain reverts and simulation errors are returned as-is without rotation.
|
|
711
|
+
*/ async forwardWithPublisherRotation(validRequests, txConfig, blobConfig) {
|
|
712
|
+
const triedAddresses = [];
|
|
713
|
+
let currentPublisher = this.l1TxUtils;
|
|
714
|
+
while(true){
|
|
715
|
+
triedAddresses.push(currentPublisher.getSenderAddress());
|
|
716
|
+
try {
|
|
717
|
+
const result = await Multicall3.forward(validRequests.map((r)=>r.request), currentPublisher, txConfig, blobConfig, this.rollupContract.address, this.log);
|
|
718
|
+
this.l1TxUtils = currentPublisher;
|
|
719
|
+
return result;
|
|
720
|
+
} catch (err) {
|
|
721
|
+
if (err instanceof TimeoutError) {
|
|
722
|
+
throw err;
|
|
723
|
+
}
|
|
724
|
+
const viemError = formatViemError(err);
|
|
725
|
+
if (!this.getNextPublisher) {
|
|
726
|
+
this.log.error('Failed to publish bundled transactions', viemError);
|
|
727
|
+
return undefined;
|
|
728
|
+
}
|
|
729
|
+
this.log.warn(`Publisher ${currentPublisher.getSenderAddress()} failed to send, rotating to next publisher`, viemError);
|
|
730
|
+
const nextPublisher = await this.getNextPublisher([
|
|
731
|
+
...triedAddresses
|
|
732
|
+
]);
|
|
733
|
+
if (!nextPublisher) {
|
|
734
|
+
this.log.error('All available publishers exhausted, failed to publish bundled transactions');
|
|
735
|
+
return undefined;
|
|
736
|
+
}
|
|
737
|
+
currentPublisher = nextPublisher;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/*
|
|
742
|
+
* Schedules sending all enqueued requests at (or after) the given timestamp.
|
|
743
|
+
* Uses InterruptibleSleep so it can be cancelled via interrupt().
|
|
744
|
+
* Returns the promise for the L1 response (caller should NOT await this in the work loop).
|
|
745
|
+
*/ async sendRequestsAt(submitAfter) {
|
|
746
|
+
const ms = submitAfter.getTime() - this.dateProvider.now();
|
|
747
|
+
if (ms > 0) {
|
|
748
|
+
this.log.debug(`Sleeping ${ms}ms before sending requests`, {
|
|
749
|
+
submitAfter
|
|
750
|
+
});
|
|
751
|
+
await this.interruptibleSleep.sleep(ms);
|
|
752
|
+
}
|
|
753
|
+
if (this.interrupted) {
|
|
754
|
+
return undefined;
|
|
755
|
+
}
|
|
756
|
+
// Re-validate enqueued requests after the sleep (state may have changed, e.g. prune or L1 reorg)
|
|
757
|
+
const validRequests = [];
|
|
758
|
+
for (const request of this.requests){
|
|
759
|
+
if (!request.preCheck) {
|
|
760
|
+
validRequests.push(request);
|
|
761
|
+
continue;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
await request.preCheck();
|
|
765
|
+
validRequests.push(request);
|
|
766
|
+
} catch (err) {
|
|
767
|
+
this.log.warn(`Pre-send validation failed for ${request.action}, discarding request`, err);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
this.requests = validRequests;
|
|
771
|
+
if (this.requests.length === 0) {
|
|
772
|
+
return undefined;
|
|
773
|
+
}
|
|
774
|
+
return this.sendRequests();
|
|
775
|
+
}
|
|
776
|
+
callbackBundledTransactions(requests, result, txContext) {
|
|
194
777
|
const actionsListStr = requests.map((r)=>r.action).join(', ');
|
|
195
778
|
if (result instanceof FormattedViemError) {
|
|
196
779
|
this.log.error(`Failed to publish bundled transactions (${actionsListStr})`, result);
|
|
780
|
+
this.backupFailedTx({
|
|
781
|
+
id: keccak256(txContext.multicallData),
|
|
782
|
+
failureType: 'send-error',
|
|
783
|
+
request: {
|
|
784
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
785
|
+
data: txContext.multicallData
|
|
786
|
+
},
|
|
787
|
+
blobData: txContext.blobData,
|
|
788
|
+
l1BlockNumber: txContext.l1BlockNumber.toString(),
|
|
789
|
+
error: {
|
|
790
|
+
message: result.message,
|
|
791
|
+
name: result.name
|
|
792
|
+
},
|
|
793
|
+
context: {
|
|
794
|
+
actions: requests.map((r)=>r.action),
|
|
795
|
+
requests: requests.map((r)=>({
|
|
796
|
+
action: r.action,
|
|
797
|
+
to: r.request.to,
|
|
798
|
+
data: r.request.data
|
|
799
|
+
})),
|
|
800
|
+
sender: this.getSenderAddress().toString()
|
|
801
|
+
}
|
|
802
|
+
});
|
|
197
803
|
return {
|
|
198
804
|
failedActions: requests.map((r)=>r.action)
|
|
199
805
|
};
|
|
200
806
|
} else {
|
|
201
807
|
this.log.verbose(`Published bundled transactions (${actionsListStr})`, {
|
|
202
808
|
result,
|
|
203
|
-
requests
|
|
809
|
+
requests: requests.map((r)=>({
|
|
810
|
+
...r,
|
|
811
|
+
// Avoid logging large blob data
|
|
812
|
+
blobConfig: r.blobConfig ? {
|
|
813
|
+
...r.blobConfig,
|
|
814
|
+
blobs: r.blobConfig.blobs.map((b)=>({
|
|
815
|
+
size: trimmedBytesLength(b)
|
|
816
|
+
}))
|
|
817
|
+
} : undefined
|
|
818
|
+
}))
|
|
204
819
|
});
|
|
205
820
|
const successfulActions = [];
|
|
206
821
|
const failedActions = [];
|
|
@@ -211,6 +826,37 @@ export class SequencerPublisher {
|
|
|
211
826
|
failedActions.push(request.action);
|
|
212
827
|
}
|
|
213
828
|
}
|
|
829
|
+
// Single backup for the whole reverted tx
|
|
830
|
+
if (failedActions.length > 0 && result?.receipt?.status === 'reverted') {
|
|
831
|
+
this.backupFailedTx({
|
|
832
|
+
id: result.receipt.transactionHash,
|
|
833
|
+
failureType: 'revert',
|
|
834
|
+
request: {
|
|
835
|
+
to: MULTI_CALL_3_ADDRESS,
|
|
836
|
+
data: txContext.multicallData
|
|
837
|
+
},
|
|
838
|
+
blobData: txContext.blobData,
|
|
839
|
+
l1BlockNumber: result.receipt.blockNumber.toString(),
|
|
840
|
+
receipt: {
|
|
841
|
+
transactionHash: result.receipt.transactionHash,
|
|
842
|
+
blockNumber: result.receipt.blockNumber.toString(),
|
|
843
|
+
gasUsed: (result.receipt.gasUsed ?? 0n).toString(),
|
|
844
|
+
status: 'reverted'
|
|
845
|
+
},
|
|
846
|
+
error: {
|
|
847
|
+
message: result.errorMsg ?? 'Transaction reverted'
|
|
848
|
+
},
|
|
849
|
+
context: {
|
|
850
|
+
actions: failedActions,
|
|
851
|
+
requests: requests.filter((r)=>failedActions.includes(r.action)).map((r)=>({
|
|
852
|
+
action: r.action,
|
|
853
|
+
to: r.request.to,
|
|
854
|
+
data: r.request.data
|
|
855
|
+
})),
|
|
856
|
+
sender: this.getSenderAddress().toString()
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
214
860
|
return {
|
|
215
861
|
successfulActions,
|
|
216
862
|
failedActions
|
|
@@ -218,19 +864,20 @@ export class SequencerPublisher {
|
|
|
218
864
|
}
|
|
219
865
|
}
|
|
220
866
|
/**
|
|
221
|
-
* @notice Will call `
|
|
867
|
+
* @notice Will call `canProposeAt` to make sure that it is possible to propose
|
|
222
868
|
* @param tipArchive - The archive to check
|
|
223
869
|
* @returns The slot and block number if it is possible to propose, undefined otherwise
|
|
224
|
-
*/
|
|
870
|
+
*/ async canProposeAt(tipArchive, msgSender, simulationOverridesPlan) {
|
|
225
871
|
// TODO: #14291 - should loop through multiple keys to check if any of them can propose
|
|
226
872
|
const ignoredErrors = [
|
|
227
873
|
'SlotAlreadyInChain',
|
|
228
874
|
'InvalidProposer',
|
|
229
875
|
'InvalidArchive'
|
|
230
876
|
];
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
877
|
+
const pipelined = this.epochCache.isProposerPipeliningEnabled();
|
|
878
|
+
const slotOffset = pipelined ? this.aztecSlotDuration : 0n;
|
|
879
|
+
const nextL1SlotTs = this.getNextL1SlotTimestamp() + slotOffset;
|
|
880
|
+
return this.rollupContract.canProposeAt(tipArchive.toBuffer(), msgSender.toString(), nextL1SlotTs, await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan)).catch((err)=>{
|
|
234
881
|
if (err instanceof FormattedViemError && ignoredErrors.find((e)=>err.message.includes(e))) {
|
|
235
882
|
this.log.warn(`Failed canProposeAtTime check with ${ignoredErrors.find((e)=>err.message.includes(e))}`, {
|
|
236
883
|
error: err.message
|
|
@@ -246,22 +893,22 @@ export class SequencerPublisher {
|
|
|
246
893
|
* @dev This is a convenience function that can be used by the sequencer to validate a "partial" header.
|
|
247
894
|
* It will throw if the block header is invalid.
|
|
248
895
|
* @param header - The block header to validate
|
|
249
|
-
*/ async validateBlockHeader(header,
|
|
896
|
+
*/ async validateBlockHeader(header, simulationOverridesPlan) {
|
|
250
897
|
const flags = {
|
|
251
898
|
ignoreDA: true,
|
|
252
899
|
ignoreSignatures: true
|
|
253
900
|
};
|
|
254
901
|
const args = [
|
|
255
902
|
header.toViem(),
|
|
256
|
-
CommitteeAttestationsAndSigners.
|
|
903
|
+
CommitteeAttestationsAndSigners.packAttestations([]),
|
|
257
904
|
[],
|
|
258
905
|
Signature.empty().toViemSignature(),
|
|
259
906
|
`0x${'0'.repeat(64)}`,
|
|
260
|
-
header.
|
|
907
|
+
header.blobsHash.toString(),
|
|
261
908
|
flags
|
|
262
909
|
];
|
|
263
|
-
const ts =
|
|
264
|
-
const stateOverrides = await this.rollupContract
|
|
910
|
+
const ts = this.getSimulationTimestamp(header.slotNumber);
|
|
911
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
265
912
|
let balance = 0n;
|
|
266
913
|
if (this.config.fishermanMode) {
|
|
267
914
|
// In fisherman mode, we can't know where the proposer is publishing from
|
|
@@ -283,39 +930,43 @@ export class SequencerPublisher {
|
|
|
283
930
|
}),
|
|
284
931
|
from: MULTI_CALL_3_ADDRESS
|
|
285
932
|
}, {
|
|
286
|
-
time: ts
|
|
933
|
+
time: ts
|
|
287
934
|
}, stateOverrides);
|
|
288
935
|
this.log.debug(`Simulated validateHeader`);
|
|
289
936
|
}
|
|
290
937
|
/**
|
|
291
|
-
* Simulate making a call to invalidate a
|
|
292
|
-
* @param
|
|
293
|
-
*/ async
|
|
938
|
+
* Simulate making a call to invalidate a checkpoint with invalid attestations. Returns undefined if no need to invalidate.
|
|
939
|
+
* @param validationResult - The validation result indicating which checkpoint to invalidate (as returned by the archiver)
|
|
940
|
+
*/ async simulateInvalidateCheckpoint(validationResult) {
|
|
294
941
|
if (validationResult.valid) {
|
|
295
942
|
return undefined;
|
|
296
943
|
}
|
|
297
|
-
const { reason,
|
|
298
|
-
const
|
|
944
|
+
const { reason, checkpoint } = validationResult;
|
|
945
|
+
const checkpointNumber = checkpoint.checkpointNumber;
|
|
299
946
|
const logData = {
|
|
300
|
-
...
|
|
947
|
+
...checkpoint,
|
|
301
948
|
reason
|
|
302
949
|
};
|
|
303
|
-
const
|
|
304
|
-
if (
|
|
305
|
-
this.log.verbose(`Skipping
|
|
306
|
-
|
|
950
|
+
const currentCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
951
|
+
if (currentCheckpointNumber < checkpointNumber) {
|
|
952
|
+
this.log.verbose(`Skipping checkpoint ${checkpointNumber} invalidation since it has already been removed from the pending chain`, {
|
|
953
|
+
currentCheckpointNumber,
|
|
307
954
|
...logData
|
|
308
955
|
});
|
|
309
956
|
return undefined;
|
|
310
957
|
}
|
|
311
|
-
const request = this.
|
|
312
|
-
this.log.debug(`Simulating invalidate
|
|
958
|
+
const request = this.buildInvalidateCheckpointRequest(validationResult);
|
|
959
|
+
this.log.debug(`Simulating invalidate checkpoint ${checkpointNumber}`, {
|
|
313
960
|
...logData,
|
|
314
961
|
request
|
|
315
962
|
});
|
|
963
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
316
964
|
try {
|
|
317
|
-
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined,
|
|
318
|
-
|
|
965
|
+
const { gasUsed } = await this.l1TxUtils.simulate(request, undefined, undefined, mergeAbis([
|
|
966
|
+
request.abi ?? [],
|
|
967
|
+
ErrorsAbi
|
|
968
|
+
]));
|
|
969
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} succeeded`, {
|
|
319
970
|
...logData,
|
|
320
971
|
request,
|
|
321
972
|
gasUsed
|
|
@@ -323,90 +974,92 @@ export class SequencerPublisher {
|
|
|
323
974
|
return {
|
|
324
975
|
request,
|
|
325
976
|
gasUsed,
|
|
326
|
-
|
|
327
|
-
|
|
977
|
+
checkpointNumber,
|
|
978
|
+
forcePendingCheckpointNumber: CheckpointNumber(checkpointNumber - 1),
|
|
979
|
+
lastArchive: validationResult.checkpoint.lastArchive,
|
|
328
980
|
reason
|
|
329
981
|
};
|
|
330
982
|
} catch (err) {
|
|
331
983
|
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
|
|
984
|
+
// If the error is due to the checkpoint not being in the pending chain, and it was indeed removed by someone else,
|
|
985
|
+
// we can safely ignore it and return undefined so we go ahead with checkpoint building.
|
|
986
|
+
if (viemError.message?.includes('Rollup__CheckpointNotInPendingChain')) {
|
|
987
|
+
this.log.verbose(`Simulation for invalidate checkpoint ${checkpointNumber} failed due to checkpoint not being in pending chain`, {
|
|
336
988
|
...logData,
|
|
337
989
|
request,
|
|
338
990
|
error: viemError.message
|
|
339
991
|
});
|
|
340
|
-
const
|
|
341
|
-
if (
|
|
342
|
-
this.log.verbose(`
|
|
992
|
+
const latestProposedCheckpointNumber = await this.rollupContract.getCheckpointNumber();
|
|
993
|
+
if (latestProposedCheckpointNumber < checkpointNumber) {
|
|
994
|
+
this.log.verbose(`Checkpoint ${checkpointNumber} has already been invalidated`, {
|
|
343
995
|
...logData
|
|
344
996
|
});
|
|
345
997
|
return undefined;
|
|
346
998
|
} else {
|
|
347
|
-
this.log.error(`Simulation for invalidate ${
|
|
348
|
-
throw new Error(`Failed to simulate invalidate
|
|
999
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed and it is still in pending chain`, viemError, logData);
|
|
1000
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber} while it is still in pending chain`, {
|
|
349
1001
|
cause: viemError
|
|
350
1002
|
});
|
|
351
1003
|
}
|
|
352
1004
|
}
|
|
353
|
-
// Otherwise, throw. We cannot build the next
|
|
354
|
-
this.log.error(`Simulation for invalidate
|
|
355
|
-
|
|
1005
|
+
// Otherwise, throw. We cannot build the next checkpoint if we cannot invalidate the previous one.
|
|
1006
|
+
this.log.error(`Simulation for invalidate checkpoint ${checkpointNumber} failed`, viemError, logData);
|
|
1007
|
+
this.backupFailedTx({
|
|
1008
|
+
id: keccak256(request.data),
|
|
1009
|
+
failureType: 'simulation',
|
|
1010
|
+
request: {
|
|
1011
|
+
to: request.to,
|
|
1012
|
+
data: request.data,
|
|
1013
|
+
value: request.value?.toString()
|
|
1014
|
+
},
|
|
1015
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1016
|
+
error: {
|
|
1017
|
+
message: viemError.message,
|
|
1018
|
+
name: viemError.name
|
|
1019
|
+
},
|
|
1020
|
+
context: {
|
|
1021
|
+
actions: [
|
|
1022
|
+
`invalidate-${reason}`
|
|
1023
|
+
],
|
|
1024
|
+
checkpointNumber,
|
|
1025
|
+
sender: this.getSenderAddress().toString()
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
throw new Error(`Failed to simulate invalidate checkpoint ${checkpointNumber}`, {
|
|
356
1029
|
cause: viemError
|
|
357
1030
|
});
|
|
358
1031
|
}
|
|
359
1032
|
}
|
|
360
|
-
|
|
1033
|
+
buildInvalidateCheckpointRequest(validationResult) {
|
|
361
1034
|
if (validationResult.valid) {
|
|
362
|
-
throw new Error('Cannot invalidate a valid
|
|
1035
|
+
throw new Error('Cannot invalidate a valid checkpoint');
|
|
363
1036
|
}
|
|
364
|
-
const {
|
|
1037
|
+
const { checkpoint, committee, reason } = validationResult;
|
|
365
1038
|
const logData = {
|
|
366
|
-
...
|
|
1039
|
+
...checkpoint,
|
|
367
1040
|
reason
|
|
368
1041
|
};
|
|
369
|
-
this.log.debug(`
|
|
370
|
-
const attestationsAndSigners =
|
|
1042
|
+
this.log.debug(`Building invalidate checkpoint ${checkpoint.checkpointNumber} request`, logData);
|
|
1043
|
+
const attestationsAndSigners = CommitteeAttestationsAndSigners.packAttestations(validationResult.attestations);
|
|
371
1044
|
if (reason === 'invalid-attestation') {
|
|
372
|
-
return this.rollupContract.buildInvalidateBadAttestationRequest(
|
|
1045
|
+
return this.rollupContract.buildInvalidateBadAttestationRequest(checkpoint.checkpointNumber, attestationsAndSigners, committee, validationResult.invalidIndex);
|
|
373
1046
|
} else if (reason === 'insufficient-attestations') {
|
|
374
|
-
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(
|
|
1047
|
+
return this.rollupContract.buildInvalidateInsufficientAttestationsRequest(checkpoint.checkpointNumber, attestationsAndSigners, committee);
|
|
375
1048
|
} else {
|
|
376
1049
|
const _ = reason;
|
|
377
1050
|
throw new Error(`Unknown reason for invalidation`);
|
|
378
1051
|
}
|
|
379
1052
|
}
|
|
380
|
-
/**
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
* @dev Throws if unable to propose
|
|
384
|
-
*
|
|
385
|
-
* @param block - The block to propose
|
|
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);
|
|
1053
|
+
/** Simulates `propose` to make sure that the checkpoint is valid for submission */ async validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan) {
|
|
1054
|
+
const blobFields = checkpoint.toBlobFields();
|
|
1055
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
403
1056
|
const blobInput = getPrefixedEthBlobCommitments(blobs);
|
|
404
1057
|
const args = [
|
|
405
1058
|
{
|
|
406
|
-
header:
|
|
407
|
-
archive: toHex(
|
|
1059
|
+
header: checkpoint.header.toViem(),
|
|
1060
|
+
archive: toHex(checkpoint.archive.root.toBuffer()),
|
|
408
1061
|
oracleInput: {
|
|
409
|
-
feeAssetPriceModifier:
|
|
1062
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
410
1063
|
}
|
|
411
1064
|
},
|
|
412
1065
|
attestationsAndSigners.getPackedAttestations(),
|
|
@@ -414,10 +1067,9 @@ export class SequencerPublisher {
|
|
|
414
1067
|
attestationsAndSignersSignature.toViemSignature(),
|
|
415
1068
|
blobInput
|
|
416
1069
|
];
|
|
417
|
-
await this.simulateProposeTx(args,
|
|
418
|
-
return ts;
|
|
1070
|
+
await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
419
1071
|
}
|
|
420
|
-
async enqueueCastSignalHelper(slotNumber,
|
|
1072
|
+
async enqueueCastSignalHelper(slotNumber, signalType, payload, base, signerAddress, signer) {
|
|
421
1073
|
if (this.lastActions[signalType] && this.lastActions[signalType] === slotNumber) {
|
|
422
1074
|
this.log.debug(`Skipping duplicate vote cast signal ${signalType} for slot ${slotNumber}`);
|
|
423
1075
|
return false;
|
|
@@ -431,9 +1083,38 @@ export class SequencerPublisher {
|
|
|
431
1083
|
}
|
|
432
1084
|
const round = await base.computeRound(slotNumber);
|
|
433
1085
|
const roundInfo = await base.getRoundInfo(this.rollupContract.address, round);
|
|
1086
|
+
if (roundInfo.quorumReached) {
|
|
1087
|
+
return false;
|
|
1088
|
+
}
|
|
434
1089
|
if (roundInfo.lastSignalSlot >= slotNumber) {
|
|
435
1090
|
return false;
|
|
436
1091
|
}
|
|
1092
|
+
if (await this.isPayloadEmpty(payload)) {
|
|
1093
|
+
this.log.warn(`Skipping vote cast for payload with empty code`);
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
// Check if payload was already submitted to governance
|
|
1097
|
+
const cacheKey = payload.toString();
|
|
1098
|
+
if (!this.payloadProposedCache.has(cacheKey)) {
|
|
1099
|
+
try {
|
|
1100
|
+
const l1StartBlock = await this.rollupContract.getL1StartBlock();
|
|
1101
|
+
const proposed = await retry(()=>base.hasPayloadBeenProposed(payload.toString(), l1StartBlock), 'Check if payload was proposed', makeBackoff([
|
|
1102
|
+
0,
|
|
1103
|
+
1,
|
|
1104
|
+
2
|
|
1105
|
+
]), this.log, true);
|
|
1106
|
+
if (proposed) {
|
|
1107
|
+
this.payloadProposedCache.add(cacheKey);
|
|
1108
|
+
}
|
|
1109
|
+
} catch (err) {
|
|
1110
|
+
this.log.warn(`Failed to check if payload ${payload} was proposed after retries, skipping signal`, err);
|
|
1111
|
+
return false;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
if (this.payloadProposedCache.has(cacheKey)) {
|
|
1115
|
+
this.log.info(`Payload ${payload} was already proposed to governance, stopping signals`);
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
437
1118
|
const cachedLastVote = this.lastActions[signalType];
|
|
438
1119
|
this.lastActions[signalType] = slotNumber;
|
|
439
1120
|
const action = signalType;
|
|
@@ -444,15 +1125,45 @@ export class SequencerPublisher {
|
|
|
444
1125
|
signer: this.l1TxUtils.client.account?.address,
|
|
445
1126
|
lastValidL2Slot: slotNumber
|
|
446
1127
|
});
|
|
1128
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1129
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
447
1130
|
try {
|
|
448
1131
|
await this.l1TxUtils.simulate(request, {
|
|
449
1132
|
time: timestamp
|
|
450
|
-
}, [],
|
|
1133
|
+
}, [], mergeAbis([
|
|
1134
|
+
request.abi ?? [],
|
|
1135
|
+
ErrorsAbi
|
|
1136
|
+
]));
|
|
451
1137
|
this.log.debug(`Simulation for ${action} at slot ${slotNumber} succeeded`, {
|
|
452
1138
|
request
|
|
453
1139
|
});
|
|
454
1140
|
} catch (err) {
|
|
455
|
-
|
|
1141
|
+
const viemError = formatViemError(err);
|
|
1142
|
+
this.log.error(`Failed simulation for ${action} at slot ${slotNumber} (enqueuing the action anyway)`, viemError, {
|
|
1143
|
+
simulationTimestamp: timestamp,
|
|
1144
|
+
l1BlockNumber
|
|
1145
|
+
});
|
|
1146
|
+
this.backupFailedTx({
|
|
1147
|
+
id: keccak256(request.data),
|
|
1148
|
+
failureType: 'simulation',
|
|
1149
|
+
request: {
|
|
1150
|
+
to: request.to,
|
|
1151
|
+
data: request.data,
|
|
1152
|
+
value: request.value?.toString()
|
|
1153
|
+
},
|
|
1154
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1155
|
+
error: {
|
|
1156
|
+
message: viemError.message,
|
|
1157
|
+
name: viemError.name
|
|
1158
|
+
},
|
|
1159
|
+
context: {
|
|
1160
|
+
actions: [
|
|
1161
|
+
action
|
|
1162
|
+
],
|
|
1163
|
+
slot: slotNumber,
|
|
1164
|
+
sender: this.getSenderAddress().toString()
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
456
1167
|
// Yes, we enqueue the request anyway, in case there was a bug with the simulation itself
|
|
457
1168
|
}
|
|
458
1169
|
// TODO(palla/slash): All votes (governance and slashing) should txTimeoutAt at the end of the slot.
|
|
@@ -472,69 +1183,41 @@ export class SequencerPublisher {
|
|
|
472
1183
|
payload: payload.toString()
|
|
473
1184
|
};
|
|
474
1185
|
if (!success) {
|
|
475
|
-
this.log.error(`Signaling in
|
|
1186
|
+
this.log.error(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} failed`, logData);
|
|
476
1187
|
this.lastActions[signalType] = cachedLastVote;
|
|
477
1188
|
return false;
|
|
478
1189
|
} else {
|
|
479
|
-
this.log.info(`Signaling in
|
|
1190
|
+
this.log.info(`Signaling in ${action} for ${payload} at slot ${slotNumber} in round ${round} succeeded`, logData);
|
|
480
1191
|
return true;
|
|
481
1192
|
}
|
|
482
1193
|
}
|
|
483
1194
|
});
|
|
484
1195
|
return true;
|
|
485
1196
|
}
|
|
1197
|
+
async isPayloadEmpty(payload) {
|
|
1198
|
+
const key = payload.toString();
|
|
1199
|
+
const cached = this.isPayloadEmptyCache.get(key);
|
|
1200
|
+
if (cached) {
|
|
1201
|
+
return cached;
|
|
1202
|
+
}
|
|
1203
|
+
const isEmpty = !await this.l1TxUtils.getCode(payload);
|
|
1204
|
+
this.isPayloadEmptyCache.set(key, isEmpty);
|
|
1205
|
+
return isEmpty;
|
|
1206
|
+
}
|
|
486
1207
|
/**
|
|
487
1208
|
* Enqueues a governance castSignal transaction to cast a signal for a given slot number.
|
|
488
1209
|
* @param slotNumber - The slot number to cast a signal for.
|
|
489
|
-
* @param timestamp - The timestamp of the slot to cast a signal for.
|
|
490
1210
|
* @returns True if the signal was successfully enqueued, false otherwise.
|
|
491
|
-
*/ enqueueGovernanceCastSignal(governancePayload, slotNumber,
|
|
492
|
-
return this.enqueueCastSignalHelper(slotNumber,
|
|
1211
|
+
*/ enqueueGovernanceCastSignal(governancePayload, slotNumber, signerAddress, signer) {
|
|
1212
|
+
return this.enqueueCastSignalHelper(slotNumber, 'governance-signal', governancePayload, this.govProposerContract, signerAddress, signer);
|
|
493
1213
|
}
|
|
494
|
-
/** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber,
|
|
1214
|
+
/** Enqueues all slashing actions as returned by the slasher client. */ async enqueueSlashingActions(actions, slotNumber, signerAddress, signer) {
|
|
495
1215
|
if (actions.length === 0) {
|
|
496
1216
|
this.log.debug(`No slashing actions to enqueue for slot ${slotNumber}`);
|
|
497
1217
|
return false;
|
|
498
1218
|
}
|
|
499
1219
|
for (const action of actions){
|
|
500
1220
|
switch(action.type){
|
|
501
|
-
case 'vote-empire-payload':
|
|
502
|
-
{
|
|
503
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
504
|
-
this.log.error('Cannot vote for empire payload on non-empire slashing contract');
|
|
505
|
-
break;
|
|
506
|
-
}
|
|
507
|
-
this.log.debug(`Enqueuing slashing vote for payload ${action.payload} at slot ${slotNumber}`, {
|
|
508
|
-
signerAddress
|
|
509
|
-
});
|
|
510
|
-
await this.enqueueCastSignalHelper(slotNumber, timestamp, 'empire-slashing-signal', action.payload, this.slashingProposerContract, signerAddress, signer);
|
|
511
|
-
break;
|
|
512
|
-
}
|
|
513
|
-
case 'create-empire-payload':
|
|
514
|
-
{
|
|
515
|
-
this.log.debug(`Enqueuing slashing create payload at slot ${slotNumber}`, {
|
|
516
|
-
slotNumber,
|
|
517
|
-
signerAddress
|
|
518
|
-
});
|
|
519
|
-
const request = this.slashFactoryContract.buildCreatePayloadRequest(action.data);
|
|
520
|
-
await this.simulateAndEnqueueRequest('create-empire-payload', request, (receipt)=>!!this.slashFactoryContract.tryExtractSlashPayloadCreatedEvent(receipt.logs), slotNumber, timestamp);
|
|
521
|
-
break;
|
|
522
|
-
}
|
|
523
|
-
case 'execute-empire-payload':
|
|
524
|
-
{
|
|
525
|
-
this.log.debug(`Enqueuing slashing execute payload at slot ${slotNumber}`, {
|
|
526
|
-
slotNumber,
|
|
527
|
-
signerAddress
|
|
528
|
-
});
|
|
529
|
-
if (this.slashingProposerContract?.type !== 'empire') {
|
|
530
|
-
this.log.error('Cannot execute slashing payload on non-empire slashing contract');
|
|
531
|
-
return false;
|
|
532
|
-
}
|
|
533
|
-
const empireSlashingProposer = this.slashingProposerContract;
|
|
534
|
-
const request = empireSlashingProposer.buildExecuteRoundRequest(action.round);
|
|
535
|
-
await this.simulateAndEnqueueRequest('execute-empire-payload', request, (receipt)=>!!empireSlashingProposer.tryExtractPayloadSubmittedEvent(receipt.logs), slotNumber, timestamp);
|
|
536
|
-
break;
|
|
537
|
-
}
|
|
538
1221
|
case 'vote-offenses':
|
|
539
1222
|
{
|
|
540
1223
|
this.log.debug(`Enqueuing slashing vote for ${action.votes.length} votes at slot ${slotNumber}`, {
|
|
@@ -543,14 +1226,13 @@ export class SequencerPublisher {
|
|
|
543
1226
|
votesCount: action.votes.length,
|
|
544
1227
|
signerAddress
|
|
545
1228
|
});
|
|
546
|
-
if (this.slashingProposerContract
|
|
547
|
-
this.log.error('
|
|
1229
|
+
if (!this.slashingProposerContract) {
|
|
1230
|
+
this.log.error('No slashing proposer contract available');
|
|
548
1231
|
return false;
|
|
549
1232
|
}
|
|
550
|
-
const tallySlashingProposer = this.slashingProposerContract;
|
|
551
1233
|
const votes = bufferToHex(encodeSlashConsensusVotes(action.votes));
|
|
552
|
-
const request = await
|
|
553
|
-
await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!
|
|
1234
|
+
const request = await this.slashingProposerContract.buildVoteRequestFromSigner(votes, slotNumber, signer);
|
|
1235
|
+
await this.simulateAndEnqueueRequest('vote-offenses', request, (receipt)=>!!this.slashingProposerContract.tryExtractVoteCastEvent(receipt.logs), slotNumber);
|
|
554
1236
|
break;
|
|
555
1237
|
}
|
|
556
1238
|
case 'execute-slash':
|
|
@@ -560,13 +1242,12 @@ export class SequencerPublisher {
|
|
|
560
1242
|
round: action.round,
|
|
561
1243
|
signerAddress
|
|
562
1244
|
});
|
|
563
|
-
if (this.slashingProposerContract
|
|
564
|
-
this.log.error('
|
|
1245
|
+
if (!this.slashingProposerContract) {
|
|
1246
|
+
this.log.error('No slashing proposer contract available');
|
|
565
1247
|
return false;
|
|
566
1248
|
}
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
await this.simulateAndEnqueueRequest('execute-slash', request, (receipt)=>!!tallySlashingProposer.tryExtractRoundExecutedEvent(receipt.logs), slotNumber, timestamp);
|
|
1249
|
+
const executeRequest = this.slashingProposerContract.buildExecuteRoundRequest(action.round, action.committees);
|
|
1250
|
+
await this.simulateAndEnqueueRequest('execute-slash', executeRequest, (receipt)=>!!this.slashingProposerContract.tryExtractRoundExecutedEvent(receipt.logs), slotNumber);
|
|
570
1251
|
break;
|
|
571
1252
|
}
|
|
572
1253
|
default:
|
|
@@ -578,60 +1259,67 @@ export class SequencerPublisher {
|
|
|
578
1259
|
}
|
|
579
1260
|
return true;
|
|
580
1261
|
}
|
|
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);
|
|
1262
|
+
/** Simulates and enqueues a proposal for a checkpoint on L1 */ async enqueueProposeCheckpoint(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, opts = {}) {
|
|
1263
|
+
const checkpointHeader = checkpoint.header;
|
|
1264
|
+
const blobFields = checkpoint.toBlobFields();
|
|
1265
|
+
const blobs = await getBlobsPerL1Block(blobFields);
|
|
590
1266
|
const proposeTxArgs = {
|
|
591
1267
|
header: checkpointHeader,
|
|
592
|
-
archive:
|
|
593
|
-
body: block.body.toBuffer(),
|
|
1268
|
+
archive: checkpoint.archive.root.toBuffer(),
|
|
594
1269
|
blobs,
|
|
595
1270
|
attestationsAndSigners,
|
|
596
|
-
attestationsAndSignersSignature
|
|
1271
|
+
attestationsAndSignersSignature,
|
|
1272
|
+
feeAssetPriceModifier: checkpoint.feeAssetPriceModifier
|
|
597
1273
|
};
|
|
598
|
-
|
|
1274
|
+
const simulationOverridesPlan = SimulationOverridesBuilder.from(opts.simulationOverridesPlan).withoutBlobCheck().build();
|
|
599
1275
|
try {
|
|
600
1276
|
// @note This will make sure that we are passing the checks for our header ASSUMING that the data is also made available
|
|
601
1277
|
// This means that we can avoid the simulation issues in later checks.
|
|
602
1278
|
// By simulation issue, I mean the fact that the block.timestamp is equal to the last block, not the next, which
|
|
603
1279
|
// make time consistency checks break.
|
|
604
1280
|
// TODO(palla): Check whether we're validating twice, once here and once within addProposeTx, since we call simulateProposeTx in both places.
|
|
605
|
-
|
|
1281
|
+
await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan);
|
|
606
1282
|
} catch (err) {
|
|
607
|
-
this.log.error(`
|
|
608
|
-
...
|
|
609
|
-
slotNumber:
|
|
610
|
-
|
|
1283
|
+
this.log.error(`Checkpoint validation failed. ${err instanceof Error ? err.message : 'No error message'}`, err, {
|
|
1284
|
+
...checkpoint.getStats(),
|
|
1285
|
+
slotNumber: checkpoint.header.slotNumber,
|
|
1286
|
+
simulationOverridesPlan
|
|
611
1287
|
});
|
|
612
1288
|
throw err;
|
|
613
1289
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
1290
|
+
// Build a pre-check callback that re-validates the checkpoint before L1 submission.
|
|
1291
|
+
// During pipelining this catches stale proposals due to prunes or L1 reorgs that occur during the pipeline sleep.
|
|
1292
|
+
let preCheck = undefined;
|
|
1293
|
+
if (this.epochCache.isProposerPipeliningEnabled()) {
|
|
1294
|
+
preCheck = async ()=>{
|
|
1295
|
+
this.log.debug(`Re-validating checkpoint ${checkpoint.number} before L1 submission`);
|
|
1296
|
+
await this.validateCheckpointForSubmission(checkpoint, attestationsAndSigners, attestationsAndSignersSignature, simulationOverridesPlan);
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
this.log.verbose(`Enqueuing checkpoint propose transaction`, {
|
|
1300
|
+
...checkpoint.toCheckpointInfo(),
|
|
1301
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1302
|
+
simulationOverridesPlan
|
|
617
1303
|
});
|
|
618
|
-
await this.addProposeTx(
|
|
619
|
-
|
|
1304
|
+
await this.addProposeTx(checkpoint, proposeTxArgs, {
|
|
1305
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
1306
|
+
simulationOverridesPlan
|
|
1307
|
+
}, preCheck);
|
|
620
1308
|
}
|
|
621
|
-
|
|
1309
|
+
enqueueInvalidateCheckpoint(request, opts = {}) {
|
|
622
1310
|
if (!request) {
|
|
623
1311
|
return;
|
|
624
1312
|
}
|
|
625
1313
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
626
1314
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(request.gasUsed) * 64 / 63)));
|
|
627
|
-
const { gasUsed,
|
|
1315
|
+
const { gasUsed, checkpointNumber } = request;
|
|
628
1316
|
const logData = {
|
|
629
1317
|
gasUsed,
|
|
630
|
-
|
|
1318
|
+
checkpointNumber,
|
|
631
1319
|
gasLimit,
|
|
632
1320
|
opts
|
|
633
1321
|
};
|
|
634
|
-
this.log.verbose(`Enqueuing invalidate
|
|
1322
|
+
this.log.verbose(`Enqueuing invalidate checkpoint request`, logData);
|
|
635
1323
|
this.addRequest({
|
|
636
1324
|
action: `invalidate-by-${request.reason}`,
|
|
637
1325
|
request: request.request,
|
|
@@ -643,12 +1331,12 @@ export class SequencerPublisher {
|
|
|
643
1331
|
checkSuccess: (_req, result)=>{
|
|
644
1332
|
const success = result && result.receipt && result.receipt.status === 'success' && tryExtractEvent(result.receipt.logs, this.rollupContract.address, RollupAbi, 'CheckpointInvalidated');
|
|
645
1333
|
if (!success) {
|
|
646
|
-
this.log.warn(`Invalidate
|
|
1334
|
+
this.log.warn(`Invalidate checkpoint ${request.checkpointNumber} failed`, {
|
|
647
1335
|
...result,
|
|
648
1336
|
...logData
|
|
649
1337
|
});
|
|
650
1338
|
} else {
|
|
651
|
-
this.log.info(`Invalidate
|
|
1339
|
+
this.log.info(`Invalidate checkpoint ${request.checkpointNumber} succeeded`, {
|
|
652
1340
|
...result,
|
|
653
1341
|
...logData
|
|
654
1342
|
});
|
|
@@ -657,7 +1345,8 @@ export class SequencerPublisher {
|
|
|
657
1345
|
}
|
|
658
1346
|
});
|
|
659
1347
|
}
|
|
660
|
-
async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber
|
|
1348
|
+
async simulateAndEnqueueRequest(action, request, checkSuccess, slotNumber) {
|
|
1349
|
+
const timestamp = this.getSimulationTimestamp(slotNumber);
|
|
661
1350
|
const logData = {
|
|
662
1351
|
slotNumber,
|
|
663
1352
|
timestamp,
|
|
@@ -670,28 +1359,60 @@ export class SequencerPublisher {
|
|
|
670
1359
|
const cachedLastActionSlot = this.lastActions[action];
|
|
671
1360
|
this.lastActions[action] = slotNumber;
|
|
672
1361
|
this.log.debug(`Simulating ${action} for slot ${slotNumber}`, logData);
|
|
1362
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
673
1363
|
let gasUsed;
|
|
1364
|
+
const simulateAbi = mergeAbis([
|
|
1365
|
+
request.abi ?? [],
|
|
1366
|
+
ErrorsAbi
|
|
1367
|
+
]);
|
|
674
1368
|
try {
|
|
675
1369
|
({ gasUsed } = await this.l1TxUtils.simulate(request, {
|
|
676
1370
|
time: timestamp
|
|
677
|
-
}, [],
|
|
1371
|
+
}, [], simulateAbi));
|
|
678
1372
|
this.log.verbose(`Simulation for ${action} succeeded`, {
|
|
679
1373
|
...logData,
|
|
680
1374
|
request,
|
|
681
1375
|
gasUsed
|
|
682
1376
|
});
|
|
683
1377
|
} catch (err) {
|
|
684
|
-
const viemError = formatViemError(err);
|
|
1378
|
+
const viemError = formatViemError(err, simulateAbi);
|
|
685
1379
|
this.log.error(`Simulation for ${action} at ${slotNumber} failed`, viemError, logData);
|
|
1380
|
+
this.backupFailedTx({
|
|
1381
|
+
id: keccak256(request.data),
|
|
1382
|
+
failureType: 'simulation',
|
|
1383
|
+
request: {
|
|
1384
|
+
to: request.to,
|
|
1385
|
+
data: request.data,
|
|
1386
|
+
value: request.value?.toString()
|
|
1387
|
+
},
|
|
1388
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1389
|
+
error: {
|
|
1390
|
+
message: viemError.message,
|
|
1391
|
+
name: viemError.name
|
|
1392
|
+
},
|
|
1393
|
+
context: {
|
|
1394
|
+
actions: [
|
|
1395
|
+
action
|
|
1396
|
+
],
|
|
1397
|
+
slot: slotNumber,
|
|
1398
|
+
sender: this.getSenderAddress().toString()
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
686
1401
|
return false;
|
|
687
1402
|
}
|
|
688
1403
|
// We issued the simulation against the rollup contract, so we need to account for the overhead of the multicall3
|
|
689
1404
|
const gasLimit = this.l1TxUtils.bumpGasLimit(BigInt(Math.ceil(Number(gasUsed) * 64 / 63)));
|
|
690
1405
|
logData.gasLimit = gasLimit;
|
|
1406
|
+
// Store the ABI used for simulation on the request so Multicall3.forward can decode errors
|
|
1407
|
+
// when the tx is sent and a revert is diagnosed via simulation.
|
|
1408
|
+
const requestWithAbi = {
|
|
1409
|
+
...request,
|
|
1410
|
+
abi: simulateAbi
|
|
1411
|
+
};
|
|
691
1412
|
this.log.debug(`Enqueuing ${action}`, logData);
|
|
692
1413
|
this.addRequest({
|
|
693
1414
|
action,
|
|
694
|
-
request,
|
|
1415
|
+
request: requestWithAbi,
|
|
695
1416
|
gasConfig: {
|
|
696
1417
|
gasLimit
|
|
697
1418
|
},
|
|
@@ -722,13 +1443,14 @@ export class SequencerPublisher {
|
|
|
722
1443
|
* A call to `restart` is required before you can continue publishing.
|
|
723
1444
|
*/ interrupt() {
|
|
724
1445
|
this.interrupted = true;
|
|
1446
|
+
this.interruptibleSleep.interrupt();
|
|
725
1447
|
this.l1TxUtils.interrupt();
|
|
726
1448
|
}
|
|
727
1449
|
/** Restarts the publisher after calling `interrupt`. */ restart() {
|
|
728
1450
|
this.interrupted = false;
|
|
729
1451
|
this.l1TxUtils.restart();
|
|
730
1452
|
}
|
|
731
|
-
async prepareProposeTx(encodedData,
|
|
1453
|
+
async prepareProposeTx(encodedData, simulationOverridesPlan) {
|
|
732
1454
|
const kzg = Blob.getViemKzgInstance();
|
|
733
1455
|
const blobInput = getPrefixedEthBlobCommitments(encodedData.blobs);
|
|
734
1456
|
this.log.debug('Validating blob input', {
|
|
@@ -755,10 +1477,38 @@ export class SequencerPublisher {
|
|
|
755
1477
|
}, {}, {
|
|
756
1478
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
757
1479
|
kzg
|
|
758
|
-
}).catch((err)=>{
|
|
759
|
-
const
|
|
760
|
-
this.log.error(`Failed to validate blobs`, message, {
|
|
761
|
-
metaMessages
|
|
1480
|
+
}).catch(async (err)=>{
|
|
1481
|
+
const viemError = formatViemError(err);
|
|
1482
|
+
this.log.error(`Failed to validate blobs`, viemError.message, {
|
|
1483
|
+
metaMessages: viemError.metaMessages
|
|
1484
|
+
});
|
|
1485
|
+
const validateBlobsData = encodeFunctionData({
|
|
1486
|
+
abi: RollupAbi,
|
|
1487
|
+
functionName: 'validateBlobs',
|
|
1488
|
+
args: [
|
|
1489
|
+
blobInput
|
|
1490
|
+
]
|
|
1491
|
+
});
|
|
1492
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1493
|
+
this.backupFailedTx({
|
|
1494
|
+
id: keccak256(validateBlobsData),
|
|
1495
|
+
failureType: 'simulation',
|
|
1496
|
+
request: {
|
|
1497
|
+
to: this.rollupContract.address,
|
|
1498
|
+
data: validateBlobsData
|
|
1499
|
+
},
|
|
1500
|
+
blobData: encodedData.blobs.map((b)=>toHex(b.data)),
|
|
1501
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1502
|
+
error: {
|
|
1503
|
+
message: viemError.message,
|
|
1504
|
+
name: viemError.name
|
|
1505
|
+
},
|
|
1506
|
+
context: {
|
|
1507
|
+
actions: [
|
|
1508
|
+
'validate-blobs'
|
|
1509
|
+
],
|
|
1510
|
+
sender: this.getSenderAddress().toString()
|
|
1511
|
+
}
|
|
762
1512
|
});
|
|
763
1513
|
throw new Error('Failed to validate blobs');
|
|
764
1514
|
});
|
|
@@ -769,8 +1519,7 @@ export class SequencerPublisher {
|
|
|
769
1519
|
header: encodedData.header.toViem(),
|
|
770
1520
|
archive: toHex(encodedData.archive),
|
|
771
1521
|
oracleInput: {
|
|
772
|
-
|
|
773
|
-
feeAssetPriceModifier: 0n
|
|
1522
|
+
feeAssetPriceModifier: encodedData.feeAssetPriceModifier
|
|
774
1523
|
}
|
|
775
1524
|
},
|
|
776
1525
|
encodedData.attestationsAndSigners.getPackedAttestations(),
|
|
@@ -778,7 +1527,7 @@ export class SequencerPublisher {
|
|
|
778
1527
|
encodedData.attestationsAndSignersSignature.toViemSignature(),
|
|
779
1528
|
blobInput
|
|
780
1529
|
];
|
|
781
|
-
const { rollupData, simulationResult } = await this.simulateProposeTx(args,
|
|
1530
|
+
const { rollupData, simulationResult } = await this.simulateProposeTx(args, simulationOverridesPlan);
|
|
782
1531
|
return {
|
|
783
1532
|
args,
|
|
784
1533
|
blobEvaluationGas,
|
|
@@ -789,29 +1538,14 @@ export class SequencerPublisher {
|
|
|
789
1538
|
/**
|
|
790
1539
|
* Simulates the propose tx with eth_simulateV1
|
|
791
1540
|
* @param args - The propose tx args
|
|
792
|
-
* @param timestamp - The timestamp to simulate proposal at
|
|
793
1541
|
* @returns The simulation result
|
|
794
|
-
*/ async simulateProposeTx(args,
|
|
1542
|
+
*/ async simulateProposeTx(args, simulationOverridesPlan) {
|
|
795
1543
|
const rollupData = encodeFunctionData({
|
|
796
1544
|
abi: RollupAbi,
|
|
797
1545
|
functionName: 'propose',
|
|
798
1546
|
args
|
|
799
1547
|
});
|
|
800
|
-
|
|
801
|
-
const forcePendingBlockNumberStateDiff = (options.forcePendingBlockNumber !== undefined ? await this.rollupContract.makePendingCheckpointNumberOverride(options.forcePendingBlockNumber) : []).flatMap((override)=>override.stateDiff ?? []);
|
|
802
|
-
const stateOverrides = [
|
|
803
|
-
{
|
|
804
|
-
address: this.rollupContract.address,
|
|
805
|
-
// @note we override checkBlob to false since blobs are not part simulate()
|
|
806
|
-
stateDiff: [
|
|
807
|
-
{
|
|
808
|
-
slot: toPaddedHex(RollupContract.checkBlobStorageSlot, true),
|
|
809
|
-
value: toPaddedHex(0n, true)
|
|
810
|
-
},
|
|
811
|
-
...forcePendingBlockNumberStateDiff
|
|
812
|
-
]
|
|
813
|
-
}
|
|
814
|
-
];
|
|
1548
|
+
const stateOverrides = await buildSimulationOverridesStateOverride(this.rollupContract, simulationOverridesPlan);
|
|
815
1549
|
// In fisherman mode, simulate as the proposer but with sufficient balance
|
|
816
1550
|
if (this.proposerAddressForSimulation) {
|
|
817
1551
|
stateOverrides.push({
|
|
@@ -819,21 +1553,22 @@ export class SequencerPublisher {
|
|
|
819
1553
|
balance: 10n * WEI_CONST * WEI_CONST
|
|
820
1554
|
});
|
|
821
1555
|
}
|
|
1556
|
+
const l1BlockNumber = await this.l1TxUtils.getBlockNumber();
|
|
1557
|
+
const simTs = this.getSimulationTimestamp(SlotNumber.fromBigInt(args[0].header.slotNumber));
|
|
822
1558
|
const simulationResult = await this.l1TxUtils.simulate({
|
|
823
1559
|
to: this.rollupContract.address,
|
|
824
1560
|
data: rollupData,
|
|
825
|
-
gas:
|
|
1561
|
+
gas: MAX_L1_TX_LIMIT,
|
|
826
1562
|
...this.proposerAddressForSimulation && {
|
|
827
1563
|
from: this.proposerAddressForSimulation.toString()
|
|
828
1564
|
}
|
|
829
1565
|
}, {
|
|
830
|
-
|
|
831
|
-
time: timestamp + 1n,
|
|
1566
|
+
time: simTs,
|
|
832
1567
|
// @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:
|
|
1568
|
+
gasLimit: MAX_L1_TX_LIMIT * 2n
|
|
834
1569
|
}, stateOverrides, RollupAbi, {
|
|
835
1570
|
// @note fallback gas estimate to use if the node doesn't support simulation API
|
|
836
|
-
fallbackGasEstimate:
|
|
1571
|
+
fallbackGasEstimate: MAX_L1_TX_LIMIT
|
|
837
1572
|
}).catch((err)=>{
|
|
838
1573
|
// In fisherman mode, we expect ValidatorSelection__MissingProposerSignature since fisherman doesn't have proposer signature
|
|
839
1574
|
const viemError = formatViemError(err);
|
|
@@ -841,11 +1576,33 @@ export class SequencerPublisher {
|
|
|
841
1576
|
this.log.debug(`Ignoring expected ValidatorSelection__MissingProposerSignature error in fisherman mode`);
|
|
842
1577
|
// Return a minimal simulation result with the fallback gas estimate
|
|
843
1578
|
return {
|
|
844
|
-
gasUsed:
|
|
1579
|
+
gasUsed: MAX_L1_TX_LIMIT,
|
|
845
1580
|
logs: []
|
|
846
1581
|
};
|
|
847
1582
|
}
|
|
848
|
-
this.log.error(`Failed to simulate propose tx`, viemError
|
|
1583
|
+
this.log.error(`Failed to simulate propose tx`, viemError, {
|
|
1584
|
+
simulationTimestamp: simTs
|
|
1585
|
+
});
|
|
1586
|
+
this.backupFailedTx({
|
|
1587
|
+
id: keccak256(rollupData),
|
|
1588
|
+
failureType: 'simulation',
|
|
1589
|
+
request: {
|
|
1590
|
+
to: this.rollupContract.address,
|
|
1591
|
+
data: rollupData
|
|
1592
|
+
},
|
|
1593
|
+
l1BlockNumber: l1BlockNumber.toString(),
|
|
1594
|
+
error: {
|
|
1595
|
+
message: viemError.message,
|
|
1596
|
+
name: viemError.name
|
|
1597
|
+
},
|
|
1598
|
+
context: {
|
|
1599
|
+
actions: [
|
|
1600
|
+
'propose'
|
|
1601
|
+
],
|
|
1602
|
+
slot: Number(args[0].header.slotNumber),
|
|
1603
|
+
sender: this.getSenderAddress().toString()
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
849
1606
|
throw err;
|
|
850
1607
|
});
|
|
851
1608
|
return {
|
|
@@ -853,28 +1610,30 @@ export class SequencerPublisher {
|
|
|
853
1610
|
simulationResult
|
|
854
1611
|
};
|
|
855
1612
|
}
|
|
856
|
-
async addProposeTx(
|
|
1613
|
+
async addProposeTx(checkpoint, encodedData, opts = {}, preCheck) {
|
|
1614
|
+
const slot = checkpoint.header.slotNumber;
|
|
857
1615
|
const timer = new Timer();
|
|
858
1616
|
const kzg = Blob.getViemKzgInstance();
|
|
859
|
-
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData,
|
|
1617
|
+
const { rollupData, simulationResult, blobEvaluationGas } = await this.prepareProposeTx(encodedData, opts.simulationOverridesPlan);
|
|
860
1618
|
const startBlock = await this.l1TxUtils.getBlockNumber();
|
|
861
1619
|
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
|
-
|
|
1620
|
+
// Send the blobs to the blob client preemptively. This helps in tests where the sequencer mistakingly thinks that the propose
|
|
1621
|
+
// tx fails but it does get mined. We make sure that the blobs are sent to the blob client regardless of the tx outcome.
|
|
1622
|
+
void Promise.resolve().then(()=>this.blobClient.sendBlobsToFilestore(encodedData.blobs).catch((_err)=>{
|
|
1623
|
+
this.log.error('Failed to send blobs to blob client');
|
|
1624
|
+
}));
|
|
867
1625
|
return this.addRequest({
|
|
868
1626
|
action: 'propose',
|
|
869
1627
|
request: {
|
|
870
1628
|
to: this.rollupContract.address,
|
|
871
1629
|
data: rollupData
|
|
872
1630
|
},
|
|
873
|
-
lastValidL2Slot:
|
|
1631
|
+
lastValidL2Slot: checkpoint.header.slotNumber,
|
|
874
1632
|
gasConfig: {
|
|
875
|
-
|
|
1633
|
+
txTimeoutAt: opts.txTimeoutAt,
|
|
876
1634
|
gasLimit
|
|
877
1635
|
},
|
|
1636
|
+
preCheck,
|
|
878
1637
|
blobConfig: {
|
|
879
1638
|
blobs: encodedData.blobs.map((b)=>b.data),
|
|
880
1639
|
kzg
|
|
@@ -898,29 +1657,36 @@ export class SequencerPublisher {
|
|
|
898
1657
|
calldataGas,
|
|
899
1658
|
calldataSize,
|
|
900
1659
|
sender,
|
|
901
|
-
...
|
|
1660
|
+
...checkpoint.getStats(),
|
|
902
1661
|
eventName: 'rollup-published-to-l1',
|
|
903
1662
|
blobCount: encodedData.blobs.length,
|
|
904
1663
|
inclusionBlocks
|
|
905
1664
|
};
|
|
906
|
-
this.log.info(`Published
|
|
1665
|
+
this.log.info(`Published checkpoint ${checkpoint.number} at slot ${slot} to rollup contract`, {
|
|
907
1666
|
...stats,
|
|
908
|
-
...
|
|
909
|
-
...receipt
|
|
1667
|
+
...checkpoint.getStats(),
|
|
1668
|
+
...pick(receipt, 'transactionHash', 'blockHash')
|
|
910
1669
|
});
|
|
911
1670
|
this.metrics.recordProcessBlockTx(timer.ms(), publishStats);
|
|
912
1671
|
return true;
|
|
913
1672
|
} else {
|
|
914
1673
|
this.metrics.recordFailedTx('process');
|
|
915
|
-
this.log.error(`
|
|
916
|
-
...
|
|
917
|
-
receipt
|
|
918
|
-
txHash: receipt.transactionHash,
|
|
919
|
-
slotNumber: block.header.globalVariables.slotNumber
|
|
1674
|
+
this.log.error(`Publishing checkpoint at slot ${slot} failed with ${errorMsg ?? 'no error message'}`, undefined, {
|
|
1675
|
+
...checkpoint.getStats(),
|
|
1676
|
+
...receipt
|
|
920
1677
|
});
|
|
921
1678
|
return false;
|
|
922
1679
|
}
|
|
923
1680
|
}
|
|
924
1681
|
});
|
|
925
1682
|
}
|
|
1683
|
+
/** Returns the timestamp of the last L1 slot within a given L2 slot. Used as the simulation timestamp
|
|
1684
|
+
* for eth_simulateV1 calls, since it's guaranteed to be greater than any L1 block produced during the slot. */ getSimulationTimestamp(slot) {
|
|
1685
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1686
|
+
return getLastL1SlotTimestampForL2Slot(slot, l1Constants);
|
|
1687
|
+
}
|
|
1688
|
+
/** Returns the timestamp of the next L1 slot boundary after now. */ getNextL1SlotTimestamp() {
|
|
1689
|
+
const l1Constants = this.epochCache.getL1Constants();
|
|
1690
|
+
return getNextL1SlotTimestamp(this.dateProvider.nowInSeconds(), l1Constants);
|
|
1691
|
+
}
|
|
926
1692
|
}
|