@aztec/aztec-node 0.0.1-commit.6c91f13 → 0.0.1-commit.96bb3f7
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/aztec-node/node_metrics.d.ts +1 -1
- package/dest/aztec-node/node_metrics.d.ts.map +1 -1
- package/dest/aztec-node/node_metrics.js +5 -16
- package/dest/aztec-node/server.d.ts +19 -11
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +443 -47
- package/dest/sentinel/sentinel.d.ts +5 -4
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +31 -26
- package/package.json +24 -25
- package/src/aztec-node/node_metrics.ts +5 -23
- package/src/aztec-node/server.ts +70 -51
- package/src/sentinel/sentinel.ts +41 -32
|
@@ -1,19 +1,385 @@
|
|
|
1
|
-
function
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
+
};
|
|
6
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, _initProto;
|
|
7
374
|
import { createArchiver } from '@aztec/archiver';
|
|
8
375
|
import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
9
|
-
import {
|
|
10
|
-
import { createReadOnlyFileStoreBlobClients, createWritableFileStoreBlobClient } from '@aztec/blob-client/filestore';
|
|
376
|
+
import { createBlobClientWithFileStores } from '@aztec/blob-client/client';
|
|
11
377
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
12
378
|
import { EpochCache } from '@aztec/epoch-cache';
|
|
13
379
|
import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
14
380
|
import { getPublicClient } from '@aztec/ethereum/client';
|
|
15
381
|
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
16
|
-
import { BlockNumber } from '@aztec/foundation/branded-types';
|
|
382
|
+
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
17
383
|
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
18
384
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
19
385
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -27,8 +393,7 @@ import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
|
|
|
27
393
|
import { createForwarderL1TxUtilsFromEthSigner, createL1TxUtilsWithBlobsFromEthSigner } from '@aztec/node-lib/factories';
|
|
28
394
|
import { createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
|
|
29
395
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
30
|
-
import { BlockBuilder, GlobalVariableBuilder, SequencerClient
|
|
31
|
-
import { CheckpointsBuilder } from '@aztec/sequencer-client';
|
|
396
|
+
import { BlockBuilder, GlobalVariableBuilder, SequencerClient } from '@aztec/sequencer-client';
|
|
32
397
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
33
398
|
import { AttestationsBlockWatcher, EpochPruneWatcher, createSlasher } from '@aztec/slasher';
|
|
34
399
|
import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
|
|
@@ -44,12 +409,15 @@ import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@az
|
|
|
44
409
|
import { PublicSimulationOutput, TxReceipt, TxStatus } from '@aztec/stdlib/tx';
|
|
45
410
|
import { getPackageVersion } from '@aztec/stdlib/update-checker';
|
|
46
411
|
import { Attributes, getTelemetryClient, trackSpan } from '@aztec/telemetry-client';
|
|
47
|
-
import { NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient } from '@aztec/validator-client';
|
|
412
|
+
import { FullNodeCheckpointsBuilder as CheckpointsBuilder, FullNodeCheckpointsBuilder, NodeKeystoreAdapter, ValidatorClient, createBlockProposalHandler, createValidatorClient, createValidatorForAcceptingTxs } from '@aztec/validator-client';
|
|
48
413
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
49
414
|
import { createPublicClient, fallback, http } from 'viem';
|
|
50
415
|
import { createSentinel } from '../sentinel/factory.js';
|
|
51
416
|
import { createKeyStoreForValidator } from './config.js';
|
|
52
417
|
import { NodeMetrics } from './node_metrics.js';
|
|
418
|
+
_dec = trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
419
|
+
[Attributes.TX_HASH]: tx.getTxHash().toString()
|
|
420
|
+
}));
|
|
53
421
|
/**
|
|
54
422
|
* The aztec node.
|
|
55
423
|
*/ export class AztecNodeService {
|
|
@@ -73,6 +441,15 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
73
441
|
telemetry;
|
|
74
442
|
log;
|
|
75
443
|
blobClient;
|
|
444
|
+
static{
|
|
445
|
+
({ e: [_initProto] } = _apply_decs_2203_r(this, [
|
|
446
|
+
[
|
|
447
|
+
_dec,
|
|
448
|
+
2,
|
|
449
|
+
"simulatePublicCalls"
|
|
450
|
+
]
|
|
451
|
+
], []));
|
|
452
|
+
}
|
|
76
453
|
metrics;
|
|
77
454
|
// Prevent two snapshot operations to happen simultaneously
|
|
78
455
|
isUploadingSnapshot;
|
|
@@ -98,7 +475,7 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
98
475
|
this.telemetry = telemetry;
|
|
99
476
|
this.log = log;
|
|
100
477
|
this.blobClient = blobClient;
|
|
101
|
-
this.isUploadingSnapshot = false;
|
|
478
|
+
this.isUploadingSnapshot = (_initProto(this), false);
|
|
102
479
|
this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
|
|
103
480
|
this.tracer = telemetry.getTracer('AztecNodeService');
|
|
104
481
|
this.log.info(`Aztec Node version: ${this.packageVersion}`);
|
|
@@ -174,20 +551,7 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
174
551
|
if (config.rollupVersion !== Number(rollupVersionFromRollup)) {
|
|
175
552
|
log.warn(`Registry looked up and returned a rollup with version (${config.rollupVersion}), but this does not match with version detected from the rollup directly: (${rollupVersionFromRollup}).`);
|
|
176
553
|
}
|
|
177
|
-
const
|
|
178
|
-
l1ChainId: config.l1ChainId,
|
|
179
|
-
rollupVersion: config.rollupVersion,
|
|
180
|
-
rollupAddress: config.l1Contracts.rollupAddress.toString()
|
|
181
|
-
};
|
|
182
|
-
const [fileStoreClients, fileStoreUploadClient] = await Promise.all([
|
|
183
|
-
createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, blobFileStoreMetadata, log),
|
|
184
|
-
createWritableFileStoreBlobClient(config.blobFileStoreUploadUrl, blobFileStoreMetadata, log)
|
|
185
|
-
]);
|
|
186
|
-
const blobClient = deps.blobClient ?? createBlobClient(config, {
|
|
187
|
-
logger: createLogger('node:blob-client:client'),
|
|
188
|
-
fileStoreClients,
|
|
189
|
-
fileStoreUploadClient
|
|
190
|
-
});
|
|
554
|
+
const blobClient = await createBlobClientWithFileStores(config, createLogger('node:blob-client:client'));
|
|
191
555
|
// attempt snapshot sync if possible
|
|
192
556
|
await trySnapshotSync(config, log);
|
|
193
557
|
const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, {
|
|
@@ -212,24 +576,32 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
212
576
|
const p2pClient = await createP2PClient(P2PClientType.Full, config, archiver, proofVerifier, worldStateSynchronizer, epochCache, packageVersion, dateProvider, telemetry, deps.p2pClientDeps);
|
|
213
577
|
// We should really not be modifying the config object
|
|
214
578
|
config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions();
|
|
579
|
+
// Create BlockBuilder for EpochPruneWatcher (slasher functionality)
|
|
215
580
|
const blockBuilder = new BlockBuilder({
|
|
216
581
|
...config,
|
|
217
582
|
l1GenesisTime,
|
|
218
583
|
slotDuration: Number(slotDuration)
|
|
219
584
|
}, worldStateSynchronizer, archiver, dateProvider, telemetry);
|
|
585
|
+
// Create FullNodeCheckpointsBuilder for validator and non-validator block proposal handling
|
|
586
|
+
const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder({
|
|
587
|
+
...config,
|
|
588
|
+
l1GenesisTime,
|
|
589
|
+
slotDuration: Number(slotDuration)
|
|
590
|
+
}, archiver, dateProvider, telemetry);
|
|
220
591
|
// We'll accumulate sentinel watchers here
|
|
221
592
|
const watchers = [];
|
|
222
593
|
// Create validator client if required
|
|
223
594
|
const validatorClient = createValidatorClient(config, {
|
|
595
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
596
|
+
worldState: worldStateSynchronizer,
|
|
224
597
|
p2pClient,
|
|
225
598
|
telemetry,
|
|
226
599
|
dateProvider,
|
|
227
600
|
epochCache,
|
|
228
|
-
blockBuilder,
|
|
229
601
|
blockSource: archiver,
|
|
230
602
|
l1ToL2MessageSource: archiver,
|
|
231
603
|
keyStoreManager,
|
|
232
|
-
|
|
604
|
+
blobClient
|
|
233
605
|
});
|
|
234
606
|
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
235
607
|
// and have it register callbacks on the p2p client *before* we start it, otherwise messages
|
|
@@ -245,7 +617,8 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
245
617
|
if (!validatorClient && config.alwaysReexecuteBlockProposals) {
|
|
246
618
|
log.info('Setting up block proposal reexecution for monitoring');
|
|
247
619
|
createBlockProposalHandler(config, {
|
|
248
|
-
|
|
620
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
621
|
+
worldState: worldStateSynchronizer,
|
|
249
622
|
epochCache,
|
|
250
623
|
blockSource: archiver,
|
|
251
624
|
l1ToL2MessageSource: archiver,
|
|
@@ -429,11 +802,20 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
429
802
|
async getPublishedBlocks(from, limit) {
|
|
430
803
|
return await this.blockSource.getPublishedBlocks(from, limit) ?? [];
|
|
431
804
|
}
|
|
805
|
+
async getPublishedCheckpoints(from, limit) {
|
|
806
|
+
return await this.blockSource.getPublishedCheckpoints(from, limit) ?? [];
|
|
807
|
+
}
|
|
808
|
+
async getL2BlocksNew(from, limit) {
|
|
809
|
+
return await this.blockSource.getL2BlocksNew(from, limit) ?? [];
|
|
810
|
+
}
|
|
811
|
+
async getCheckpointedBlocks(from, limit, proven) {
|
|
812
|
+
return await this.blockSource.getCheckpointedBlocks(from, limit, proven) ?? [];
|
|
813
|
+
}
|
|
432
814
|
/**
|
|
433
|
-
* Method to fetch the current
|
|
434
|
-
* @returns The current
|
|
435
|
-
*/ async
|
|
436
|
-
return await this.globalVariableBuilder.
|
|
815
|
+
* Method to fetch the current min L2 fees.
|
|
816
|
+
* @returns The current min L2 fees.
|
|
817
|
+
*/ async getCurrentMinFees() {
|
|
818
|
+
return await this.globalVariableBuilder.getCurrentMinFees();
|
|
437
819
|
}
|
|
438
820
|
async getMaxPriorityFees() {
|
|
439
821
|
for await (const tx of this.p2pClient.iteratePendingTxs()){
|
|
@@ -555,6 +937,12 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
555
937
|
this.log.info(`Stopped Aztec Node`);
|
|
556
938
|
}
|
|
557
939
|
/**
|
|
940
|
+
* Returns the blob client used by this node.
|
|
941
|
+
* @internal - Exposed for testing purposes only.
|
|
942
|
+
*/ getBlobClient() {
|
|
943
|
+
return this.blobClient;
|
|
944
|
+
}
|
|
945
|
+
/**
|
|
558
946
|
* Method to retrieve pending txs.
|
|
559
947
|
* @param limit - The number of items to returns
|
|
560
948
|
* @param after - The last known pending tx. Used for pagination
|
|
@@ -699,12 +1087,25 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
699
1087
|
return messageIndex !== undefined;
|
|
700
1088
|
}
|
|
701
1089
|
/**
|
|
702
|
-
* Returns all the L2 to L1 messages in
|
|
703
|
-
* @param
|
|
704
|
-
* @returns The L2 to L1 messages (
|
|
705
|
-
*/ async getL2ToL1Messages(
|
|
706
|
-
|
|
707
|
-
|
|
1090
|
+
* Returns all the L2 to L1 messages in an epoch.
|
|
1091
|
+
* @param epoch - The epoch at which to get the data.
|
|
1092
|
+
* @returns The L2 to L1 messages (empty array if the epoch is not found).
|
|
1093
|
+
*/ async getL2ToL1Messages(epoch) {
|
|
1094
|
+
// Assumes `getBlocksForEpoch` returns blocks in ascending order of block number.
|
|
1095
|
+
const blocks = await this.blockSource.getBlocksForEpoch(epoch);
|
|
1096
|
+
const blocksInCheckpoints = [];
|
|
1097
|
+
let previousSlotNumber = SlotNumber.ZERO;
|
|
1098
|
+
let checkpointIndex = -1;
|
|
1099
|
+
for (const block of blocks){
|
|
1100
|
+
const slotNumber = block.header.globalVariables.slotNumber;
|
|
1101
|
+
if (slotNumber !== previousSlotNumber) {
|
|
1102
|
+
checkpointIndex++;
|
|
1103
|
+
blocksInCheckpoints.push([]);
|
|
1104
|
+
previousSlotNumber = slotNumber;
|
|
1105
|
+
}
|
|
1106
|
+
blocksInCheckpoints[checkpointIndex].push(block);
|
|
1107
|
+
}
|
|
1108
|
+
return blocksInCheckpoints.map((blocks)=>blocks.map((block)=>block.body.txEffects.map((txEffect)=>txEffect.l2ToL1Msgs)));
|
|
708
1109
|
}
|
|
709
1110
|
/**
|
|
710
1111
|
* Returns a sibling path for a leaf in the committed blocks tree.
|
|
@@ -887,7 +1288,7 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
887
1288
|
l1ChainId: this.l1ChainId,
|
|
888
1289
|
rollupVersion: this.version,
|
|
889
1290
|
setupAllowList: this.config.txPublicSetupAllowList ?? await getDefaultAllowedSetupFunctions(),
|
|
890
|
-
gasFees: await this.
|
|
1291
|
+
gasFees: await this.getCurrentMinFees(),
|
|
891
1292
|
skipFeeEnforcement,
|
|
892
1293
|
txsPermitted: !this.config.disableTransactions
|
|
893
1294
|
});
|
|
@@ -950,7 +1351,7 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
950
1351
|
throw new Error(`Archiver initial sync not complete. Cannot start snapshot.`);
|
|
951
1352
|
}
|
|
952
1353
|
// And it has an L2 block hash
|
|
953
|
-
const l2BlockHash = await archiver.getL2Tips().then((tips)=>tips.
|
|
1354
|
+
const l2BlockHash = await archiver.getL2Tips().then((tips)=>tips.proposed.hash);
|
|
954
1355
|
if (!l2BlockHash) {
|
|
955
1356
|
this.metrics.recordSnapshotError();
|
|
956
1357
|
throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
|
|
@@ -977,7 +1378,7 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
977
1378
|
if (!('rollbackTo' in archiver)) {
|
|
978
1379
|
throw new Error('Archiver implementation does not support rollbacks.');
|
|
979
1380
|
}
|
|
980
|
-
const finalizedBlock = await archiver.getL2Tips().then((tips)=>tips.finalized.number);
|
|
1381
|
+
const finalizedBlock = await archiver.getL2Tips().then((tips)=>tips.finalized.block.number);
|
|
981
1382
|
if (targetBlock < finalizedBlock) {
|
|
982
1383
|
if (force) {
|
|
983
1384
|
this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
|
|
@@ -1066,8 +1467,3 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
1066
1467
|
return await this.worldStateSynchronizer.syncImmediate(blockSourceHeight);
|
|
1067
1468
|
}
|
|
1068
1469
|
}
|
|
1069
|
-
_ts_decorate([
|
|
1070
|
-
trackSpan('AztecNodeService.simulatePublicCalls', (tx)=>({
|
|
1071
|
-
[Attributes.TX_HASH]: tx.getTxHash().toString()
|
|
1072
|
-
}))
|
|
1073
|
-
], AztecNodeService.prototype, "simulatePublicCalls", null);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import {
|
|
2
|
+
import { CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
4
4
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
5
5
|
import { type L2TipsStore } from '@aztec/kv-store/stores';
|
|
@@ -22,8 +22,8 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
22
22
|
protected l2TipsStore: L2TipsStore;
|
|
23
23
|
protected initialSlot: SlotNumber | undefined;
|
|
24
24
|
protected lastProcessedSlot: SlotNumber | undefined;
|
|
25
|
-
protected
|
|
26
|
-
|
|
25
|
+
protected slotNumberToCheckpoint: Map<SlotNumber, {
|
|
26
|
+
checkpointNumber: CheckpointNumber;
|
|
27
27
|
archive: string;
|
|
28
28
|
attestors: EthAddress[];
|
|
29
29
|
}>;
|
|
@@ -34,6 +34,7 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
34
34
|
protected init(): Promise<void>;
|
|
35
35
|
stop(): Promise<void>;
|
|
36
36
|
handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void>;
|
|
37
|
+
protected handleCheckpoint(event: L2BlockStreamEvent): void;
|
|
37
38
|
protected handleChainProven(event: L2BlockStreamEvent): Promise<void>;
|
|
38
39
|
protected computeProvenPerformance(epoch: EpochNumber): Promise<ValidatorsEpochPerformance>;
|
|
39
40
|
/**
|
|
@@ -89,4 +90,4 @@ export declare class Sentinel extends Sentinel_base implements L2BlockStreamEven
|
|
|
89
90
|
} | undefined;
|
|
90
91
|
}
|
|
91
92
|
export {};
|
|
92
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
93
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VudGluZWwuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZW50aW5lbC9zZW50aW5lbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxVQUFVLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNyRCxPQUFPLEVBQWUsZ0JBQWdCLEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBRXpHLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSwrQkFBK0IsQ0FBQztBQUUzRCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sbUNBQW1DLENBQUM7QUFDbkUsT0FBTyxFQUFxQixLQUFLLFdBQVcsRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzdFLE9BQU8sS0FBSyxFQUFFLFNBQVMsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUM1QyxPQUFPLEVBSUwsS0FBSyxPQUFPLEVBQ1osS0FBSyxjQUFjLEVBQ3BCLE1BQU0sZ0JBQWdCLENBQUM7QUFDeEIsT0FBTyxLQUFLLEVBQUUsYUFBYSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFDM0QsT0FBTyxFQUNMLEtBQUssYUFBYSxFQUNsQixhQUFhLEVBQ2IsS0FBSyxrQkFBa0IsRUFDdkIsS0FBSyx5QkFBeUIsRUFFL0IsTUFBTSxxQkFBcUIsQ0FBQztBQUU3QixPQUFPLEtBQUssRUFDVixvQkFBb0IsRUFDcEIsY0FBYyxFQUNkLHNCQUFzQixFQUN0QixxQkFBcUIsRUFDckIsbUJBQW1CLEVBQ25CLDBCQUEwQixFQUMxQixlQUFlLEVBQ2hCLE1BQU0sMEJBQTBCLENBQUM7QUFJbEMsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLFlBQVksQ0FBQzs7QUFFM0MscUJBQWEsUUFBUyxTQUFRLGFBQTJDLFlBQVcseUJBQXlCLEVBQUUsT0FBTztJQWNsSCxTQUFTLENBQUMsVUFBVSxFQUFFLFVBQVU7SUFDaEMsU0FBUyxDQUFDLFFBQVEsRUFBRSxhQUFhO0lB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
|