@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,
|
|
1
|
+
{"version":3,"file":"sentinel.d.ts","sourceRoot":"","sources":["../../src/sentinel/sentinel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAe,gBAAgB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAEzG,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACnE,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EACL,KAAK,aAAa,EAClB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,yBAAyB,EAE/B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,0BAA0B,EAC1B,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAIlC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;;AAE3C,qBAAa,QAAS,SAAQ,aAA2C,YAAW,yBAAyB,EAAE,OAAO;IAclH,SAAS,CAAC,UAAU,EAAE,UAAU;IAChC,SAAS,CAAC,QAAQ,EAAE,aAAa;IACjC,SAAS,CAAC,GAAG,EAAE,SAAS;IACxB,SAAS,CAAC,KAAK,EAAE,aAAa;IAC9B,SAAS,CAAC,MAAM,EAAE,IAAI,CACpB,aAAa,EACb,iCAAiC,GAAG,wBAAwB,GAAG,0CAA0C,CAC1G;IACD,SAAS,CAAC,MAAM;IArBlB,SAAS,CAAC,cAAc,EAAE,cAAc,CAAC;IACzC,SAAS,CAAC,WAAW,EAAG,aAAa,CAAC;IACtC,SAAS,CAAC,WAAW,EAAE,WAAW,CAAC;IAEnC,SAAS,CAAC,WAAW,EAAE,UAAU,GAAG,SAAS,CAAC;IAC9C,SAAS,CAAC,iBAAiB,EAAE,UAAU,GAAG,SAAS,CAAC;IAEpD,SAAS,CAAC,sBAAsB,EAAE,GAAG,CACnC,UAAU,EACV;QAAE,gBAAgB,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,UAAU,EAAE,CAAA;KAAE,CACjF,CAAa;IAEd,YACY,UAAU,EAAE,UAAU,EACtB,QAAQ,EAAE,aAAa,EACvB,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,aAAa,EACpB,MAAM,EAAE,IAAI,CACpB,aAAa,EACb,iCAAiC,GAAG,wBAAwB,GAAG,0CAA0C,CAC1G,EACS,MAAM,yCAAgC,EAMjD;IAEM,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,QAEjD;IAEY,KAAK,kBAGjB;IAED,kHAAkH;IAClH,UAAgB,IAAI,kBAKnB;IAEM,IAAI,kBAEV;IAEY,sBAAsB,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAO5E;IAED,SAAS,CAAC,gBAAgB,CAAC,KAAK,EAAE,kBAAkB,QAyBnD;IAED,UAAgB,iBAAiB,CAAC,KAAK,EAAE,kBAAkB,iBAoB1D;IAED,UAAgB,wBAAwB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAyBhG;IAED;;;;;OAKG;IACH,UAAgB,mBAAmB,CACjC,SAAS,EAAE,UAAU,EACrB,YAAY,EAAE,WAAW,EACzB,yBAAyB,EAAE,MAAM,GAChC,OAAO,CAAC,OAAO,CAAC,CAuBlB;IAED,UAAgB,uBAAuB,CAAC,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,0BAA0B,iBAkClG;IAED;;;;OAIG;IACU,IAAI,kBAiBhB;IAED;;;;OAIG;IACH,UAAgB,gBAAgB,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,CAqCrF;IAED;;;OAGG;IACH,UAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,iBAa3C;IAED,0CAA0C;IAC1C,UAAgB,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE;;OA2DlH;IAED,wDAAwD;IACxD,SAAS,CAAC,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,qBAAqB,GAAG,SAAS,CAAC,iBAE3G;IAED,0DAA0D;IAC7C,YAAY,CAAC,EACxB,QAAQ,EACR,MAAM,EACN,UAAU,EACX,GAAE;QAAE,QAAQ,CAAC,EAAE,UAAU,CAAC;QAAC,MAAM,CAAC,EAAE,UAAU,CAAC;QAAC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAA;KAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAmB3G;IAED,6CAA6C;IAChC,iBAAiB,CAC5B,gBAAgB,EAAE,UAAU,EAC5B,QAAQ,CAAC,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAkC3C;IAED,SAAS,CAAC,wBAAwB,CAChC,OAAO,EAAE,KAAK,MAAM,EAAE,EACtB,UAAU,EAAE,sBAAsB,EAClC,QAAQ,CAAC,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,UAAU,GAClB,cAAc,CAchB;IAED,SAAS,CAAC,aAAa,CACrB,OAAO,EAAE,sBAAsB,EAC/B,iBAAiB,EAAE,mBAAmB,GAAG,SAAS,EAClD,MAAM,EAAE,qBAAqB,EAAE;;;;;MAUhC;IAED,SAAS,CAAC,eAAe,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS;;;;kBAMrD;CACF"}
|
|
@@ -5,7 +5,7 @@ import { createLogger } from '@aztec/foundation/log';
|
|
|
5
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
6
6
|
import { L2TipsMemoryStore } from '@aztec/kv-store/stores';
|
|
7
7
|
import { OffenseType, WANT_TO_SLASH_EVENT } from '@aztec/slasher';
|
|
8
|
-
import { L2BlockStream,
|
|
8
|
+
import { L2BlockStream, getAttestationInfoFromPublishedCheckpoint } from '@aztec/stdlib/block';
|
|
9
9
|
import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
10
10
|
import EventEmitter from 'node:events';
|
|
11
11
|
export class Sentinel extends EventEmitter {
|
|
@@ -21,9 +21,9 @@ export class Sentinel extends EventEmitter {
|
|
|
21
21
|
initialSlot;
|
|
22
22
|
lastProcessedSlot;
|
|
23
23
|
// eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
|
|
24
|
-
|
|
24
|
+
slotNumberToCheckpoint;
|
|
25
25
|
constructor(epochCache, archiver, p2p, store, config, logger = createLogger('node:sentinel')){
|
|
26
|
-
super(), this.epochCache = epochCache, this.archiver = archiver, this.p2p = p2p, this.store = store, this.config = config, this.logger = logger, this.
|
|
26
|
+
super(), this.epochCache = epochCache, this.archiver = archiver, this.p2p = p2p, this.store = store, this.config = config, this.logger = logger, this.slotNumberToCheckpoint = new Map();
|
|
27
27
|
this.l2TipsStore = new L2TipsMemoryStore();
|
|
28
28
|
const interval = epochCache.getL1Constants().ethereumSlotDuration * 1000 / 4;
|
|
29
29
|
this.runningPromise = new RunningPromise(this.work.bind(this), logger, interval);
|
|
@@ -51,33 +51,38 @@ export class Sentinel extends EventEmitter {
|
|
|
51
51
|
}
|
|
52
52
|
async handleBlockStreamEvent(event) {
|
|
53
53
|
await this.l2TipsStore.handleBlockStreamEvent(event);
|
|
54
|
-
if (event.type === '
|
|
55
|
-
|
|
56
|
-
for (const block of event.blocks){
|
|
57
|
-
this.slotNumberToBlock.set(block.block.header.getSlot(), {
|
|
58
|
-
blockNumber: BlockNumber(block.block.number),
|
|
59
|
-
archive: block.block.archive.root.toString(),
|
|
60
|
-
attestors: getAttestationInfoFromPublishedL2Block(block).filter((a)=>a.status === 'recovered-from-signature').map((a)=>a.address)
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
// Prune the archive map to only keep at most N entries
|
|
64
|
-
const historyLength = this.store.getHistoryLength();
|
|
65
|
-
if (this.slotNumberToBlock.size > historyLength) {
|
|
66
|
-
const toDelete = Array.from(this.slotNumberToBlock.keys()).sort((a, b)=>Number(a - b)).slice(0, this.slotNumberToBlock.size - historyLength);
|
|
67
|
-
for (const key of toDelete){
|
|
68
|
-
this.slotNumberToBlock.delete(key);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
54
|
+
if (event.type === 'chain-checkpointed') {
|
|
55
|
+
this.handleCheckpoint(event);
|
|
71
56
|
} else if (event.type === 'chain-proven') {
|
|
72
57
|
await this.handleChainProven(event);
|
|
73
58
|
}
|
|
74
59
|
}
|
|
60
|
+
handleCheckpoint(event) {
|
|
61
|
+
if (event.type !== 'chain-checkpointed') {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const checkpoint = event.checkpoint;
|
|
65
|
+
// Store mapping from slot to archive, checkpoint number, and attestors
|
|
66
|
+
this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, {
|
|
67
|
+
checkpointNumber: checkpoint.checkpoint.number,
|
|
68
|
+
archive: checkpoint.checkpoint.archive.root.toString(),
|
|
69
|
+
attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint).filter((a)=>a.status === 'recovered-from-signature').map((a)=>a.address)
|
|
70
|
+
});
|
|
71
|
+
// Prune the archive map to only keep at most N entries
|
|
72
|
+
const historyLength = this.store.getHistoryLength();
|
|
73
|
+
if (this.slotNumberToCheckpoint.size > historyLength) {
|
|
74
|
+
const toDelete = Array.from(this.slotNumberToCheckpoint.keys()).sort((a, b)=>Number(a - b)).slice(0, this.slotNumberToCheckpoint.size - historyLength);
|
|
75
|
+
for (const key of toDelete){
|
|
76
|
+
this.slotNumberToCheckpoint.delete(key);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
75
80
|
async handleChainProven(event) {
|
|
76
81
|
if (event.type !== 'chain-proven') {
|
|
77
82
|
return;
|
|
78
83
|
}
|
|
79
|
-
const blockNumber =
|
|
80
|
-
const block = await this.archiver.
|
|
84
|
+
const blockNumber = event.block.number;
|
|
85
|
+
const block = await this.archiver.getL2BlockNew(blockNumber);
|
|
81
86
|
if (!block) {
|
|
82
87
|
this.logger.error(`Failed to get block ${blockNumber}`, {
|
|
83
88
|
block
|
|
@@ -221,8 +226,8 @@ export class Sentinel extends EventEmitter {
|
|
|
221
226
|
});
|
|
222
227
|
return false;
|
|
223
228
|
}
|
|
224
|
-
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then((tip)=>tip.
|
|
225
|
-
const p2pLastBlockHash = await this.p2p.getL2Tips().then((tips)=>tips.
|
|
229
|
+
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then((tip)=>tip.proposed.hash);
|
|
230
|
+
const p2pLastBlockHash = await this.p2p.getL2Tips().then((tips)=>tips.proposed.hash);
|
|
226
231
|
const isP2pSynced = archiverLastBlockHash === p2pLastBlockHash;
|
|
227
232
|
if (!isP2pSynced) {
|
|
228
233
|
this.logger.debug(`Waiting for P2P client to sync with archiver`, {
|
|
@@ -262,8 +267,8 @@ export class Sentinel extends EventEmitter {
|
|
|
262
267
|
// or all attestations for all proposals in the slot if no block was mined.
|
|
263
268
|
// We gather from both p2p (contains the ones seen on the p2p layer) and archiver
|
|
264
269
|
// (contains the ones synced from mined blocks, which we may have missed from p2p).
|
|
265
|
-
const block = this.
|
|
266
|
-
const p2pAttested = await this.p2p.
|
|
270
|
+
const block = this.slotNumberToCheckpoint.get(slot);
|
|
271
|
+
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, block?.archive);
|
|
267
272
|
// Filter out attestations with invalid signatures
|
|
268
273
|
const p2pAttestors = p2pAttested.map((a)=>a.getSender()).filter((s)=>s !== undefined);
|
|
269
274
|
const attestors = new Set([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/aztec-node",
|
|
3
|
-
"version": "0.0.1-commit.
|
|
3
|
+
"version": "0.0.1-commit.96bb3f7",
|
|
4
4
|
"main": "dest/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
"./sentinel": "./dest/aztec-node/sentinel.js",
|
|
10
10
|
"./test": "./dest/test/index.js"
|
|
11
11
|
},
|
|
12
|
-
"bin": "./dest/bin/index.js",
|
|
13
12
|
"typedocOptions": {
|
|
14
13
|
"entryPoints": [
|
|
15
14
|
"./src/index.ts"
|
|
@@ -66,29 +65,29 @@
|
|
|
66
65
|
]
|
|
67
66
|
},
|
|
68
67
|
"dependencies": {
|
|
69
|
-
"@aztec/archiver": "0.0.1-commit.
|
|
70
|
-
"@aztec/bb-prover": "0.0.1-commit.
|
|
71
|
-
"@aztec/blob-client": "0.0.1-commit.
|
|
72
|
-
"@aztec/constants": "0.0.1-commit.
|
|
73
|
-
"@aztec/epoch-cache": "0.0.1-commit.
|
|
74
|
-
"@aztec/ethereum": "0.0.1-commit.
|
|
75
|
-
"@aztec/foundation": "0.0.1-commit.
|
|
76
|
-
"@aztec/kv-store": "0.0.1-commit.
|
|
77
|
-
"@aztec/l1-artifacts": "0.0.1-commit.
|
|
78
|
-
"@aztec/merkle-tree": "0.0.1-commit.
|
|
79
|
-
"@aztec/node-keystore": "0.0.1-commit.
|
|
80
|
-
"@aztec/node-lib": "0.0.1-commit.
|
|
81
|
-
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.
|
|
82
|
-
"@aztec/p2p": "0.0.1-commit.
|
|
83
|
-
"@aztec/protocol-contracts": "0.0.1-commit.
|
|
84
|
-
"@aztec/prover-client": "0.0.1-commit.
|
|
85
|
-
"@aztec/sequencer-client": "0.0.1-commit.
|
|
86
|
-
"@aztec/simulator": "0.0.1-commit.
|
|
87
|
-
"@aztec/slasher": "0.0.1-commit.
|
|
88
|
-
"@aztec/stdlib": "0.0.1-commit.
|
|
89
|
-
"@aztec/telemetry-client": "0.0.1-commit.
|
|
90
|
-
"@aztec/validator-client": "0.0.1-commit.
|
|
91
|
-
"@aztec/world-state": "0.0.1-commit.
|
|
68
|
+
"@aztec/archiver": "0.0.1-commit.96bb3f7",
|
|
69
|
+
"@aztec/bb-prover": "0.0.1-commit.96bb3f7",
|
|
70
|
+
"@aztec/blob-client": "0.0.1-commit.96bb3f7",
|
|
71
|
+
"@aztec/constants": "0.0.1-commit.96bb3f7",
|
|
72
|
+
"@aztec/epoch-cache": "0.0.1-commit.96bb3f7",
|
|
73
|
+
"@aztec/ethereum": "0.0.1-commit.96bb3f7",
|
|
74
|
+
"@aztec/foundation": "0.0.1-commit.96bb3f7",
|
|
75
|
+
"@aztec/kv-store": "0.0.1-commit.96bb3f7",
|
|
76
|
+
"@aztec/l1-artifacts": "0.0.1-commit.96bb3f7",
|
|
77
|
+
"@aztec/merkle-tree": "0.0.1-commit.96bb3f7",
|
|
78
|
+
"@aztec/node-keystore": "0.0.1-commit.96bb3f7",
|
|
79
|
+
"@aztec/node-lib": "0.0.1-commit.96bb3f7",
|
|
80
|
+
"@aztec/noir-protocol-circuits-types": "0.0.1-commit.96bb3f7",
|
|
81
|
+
"@aztec/p2p": "0.0.1-commit.96bb3f7",
|
|
82
|
+
"@aztec/protocol-contracts": "0.0.1-commit.96bb3f7",
|
|
83
|
+
"@aztec/prover-client": "0.0.1-commit.96bb3f7",
|
|
84
|
+
"@aztec/sequencer-client": "0.0.1-commit.96bb3f7",
|
|
85
|
+
"@aztec/simulator": "0.0.1-commit.96bb3f7",
|
|
86
|
+
"@aztec/slasher": "0.0.1-commit.96bb3f7",
|
|
87
|
+
"@aztec/stdlib": "0.0.1-commit.96bb3f7",
|
|
88
|
+
"@aztec/telemetry-client": "0.0.1-commit.96bb3f7",
|
|
89
|
+
"@aztec/validator-client": "0.0.1-commit.96bb3f7",
|
|
90
|
+
"@aztec/world-state": "0.0.1-commit.96bb3f7",
|
|
92
91
|
"koa": "^2.16.1",
|
|
93
92
|
"koa-router": "^13.1.1",
|
|
94
93
|
"tslib": "^2.4.0",
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Attributes,
|
|
3
|
-
type Histogram,
|
|
4
|
-
Metrics,
|
|
5
|
-
type TelemetryClient,
|
|
6
|
-
type UpDownCounter,
|
|
7
|
-
ValueType,
|
|
8
|
-
} from '@aztec/telemetry-client';
|
|
1
|
+
import { Attributes, type Histogram, Metrics, type TelemetryClient, type UpDownCounter } from '@aztec/telemetry-client';
|
|
9
2
|
|
|
10
3
|
export class NodeMetrics {
|
|
11
4
|
private receiveTxCount: UpDownCounter;
|
|
@@ -16,23 +9,12 @@ export class NodeMetrics {
|
|
|
16
9
|
|
|
17
10
|
constructor(client: TelemetryClient, name = 'AztecNode') {
|
|
18
11
|
const meter = client.getMeter(name);
|
|
19
|
-
this.receiveTxCount = meter.createUpDownCounter(Metrics.NODE_RECEIVE_TX_COUNT
|
|
20
|
-
this.receiveTxDuration = meter.createHistogram(Metrics.NODE_RECEIVE_TX_DURATION
|
|
21
|
-
description: 'The duration of the receiveTx method',
|
|
22
|
-
unit: 'ms',
|
|
23
|
-
valueType: ValueType.INT,
|
|
24
|
-
});
|
|
12
|
+
this.receiveTxCount = meter.createUpDownCounter(Metrics.NODE_RECEIVE_TX_COUNT);
|
|
13
|
+
this.receiveTxDuration = meter.createHistogram(Metrics.NODE_RECEIVE_TX_DURATION);
|
|
25
14
|
|
|
26
|
-
this.snapshotDuration = meter.createHistogram(Metrics.NODE_SNAPSHOT_DURATION
|
|
27
|
-
description: 'How long taking a snapshot takes',
|
|
28
|
-
unit: 'ms',
|
|
29
|
-
valueType: ValueType.INT,
|
|
30
|
-
});
|
|
15
|
+
this.snapshotDuration = meter.createHistogram(Metrics.NODE_SNAPSHOT_DURATION);
|
|
31
16
|
|
|
32
|
-
this.snapshotErrorCount = meter.createUpDownCounter(Metrics.NODE_SNAPSHOT_ERROR_COUNT
|
|
33
|
-
description: 'How many snapshot errors have happened',
|
|
34
|
-
valueType: ValueType.INT,
|
|
35
|
-
});
|
|
17
|
+
this.snapshotErrorCount = meter.createUpDownCounter(Metrics.NODE_SNAPSHOT_ERROR_COUNT);
|
|
36
18
|
|
|
37
19
|
this.snapshotErrorCount.add(0);
|
|
38
20
|
}
|
package/src/aztec-node/server.ts
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { Archiver, createArchiver } from '@aztec/archiver';
|
|
2
2
|
import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
3
|
-
import { type BlobClientInterface,
|
|
4
|
-
import {
|
|
5
|
-
type BlobFileStoreMetadata,
|
|
6
|
-
createReadOnlyFileStoreBlobClients,
|
|
7
|
-
createWritableFileStoreBlobClient,
|
|
8
|
-
} from '@aztec/blob-client/filestore';
|
|
3
|
+
import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client';
|
|
9
4
|
import {
|
|
10
5
|
ARCHIVE_HEIGHT,
|
|
11
6
|
INITIAL_L2_BLOCK_NUM,
|
|
@@ -19,7 +14,7 @@ import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
|
19
14
|
import { getPublicClient } from '@aztec/ethereum/client';
|
|
20
15
|
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
21
16
|
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
22
|
-
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
17
|
+
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
23
18
|
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
24
19
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
25
20
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -36,14 +31,7 @@ import {
|
|
|
36
31
|
} from '@aztec/node-lib/factories';
|
|
37
32
|
import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
|
|
38
33
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
39
|
-
import {
|
|
40
|
-
BlockBuilder,
|
|
41
|
-
GlobalVariableBuilder,
|
|
42
|
-
SequencerClient,
|
|
43
|
-
type SequencerPublisher,
|
|
44
|
-
createValidatorForAcceptingTxs,
|
|
45
|
-
} from '@aztec/sequencer-client';
|
|
46
|
-
import { CheckpointsBuilder } from '@aztec/sequencer-client';
|
|
34
|
+
import { BlockBuilder, GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
|
|
47
35
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
48
36
|
import {
|
|
49
37
|
AttestationsBlockWatcher,
|
|
@@ -59,9 +47,11 @@ import {
|
|
|
59
47
|
type DataInBlock,
|
|
60
48
|
type L2Block,
|
|
61
49
|
L2BlockHash,
|
|
50
|
+
L2BlockNew,
|
|
62
51
|
type L2BlockSource,
|
|
63
52
|
type PublishedL2Block,
|
|
64
53
|
} from '@aztec/stdlib/block';
|
|
54
|
+
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
65
55
|
import type {
|
|
66
56
|
ContractClassPublic,
|
|
67
57
|
ContractDataSource,
|
|
@@ -116,10 +106,13 @@ import {
|
|
|
116
106
|
trackSpan,
|
|
117
107
|
} from '@aztec/telemetry-client';
|
|
118
108
|
import {
|
|
109
|
+
FullNodeCheckpointsBuilder as CheckpointsBuilder,
|
|
110
|
+
FullNodeCheckpointsBuilder,
|
|
119
111
|
NodeKeystoreAdapter,
|
|
120
112
|
ValidatorClient,
|
|
121
113
|
createBlockProposalHandler,
|
|
122
114
|
createValidatorClient,
|
|
115
|
+
createValidatorForAcceptingTxs,
|
|
123
116
|
} from '@aztec/validator-client';
|
|
124
117
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
125
118
|
|
|
@@ -191,7 +184,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
191
184
|
logger?: Logger;
|
|
192
185
|
publisher?: SequencerPublisher;
|
|
193
186
|
dateProvider?: DateProvider;
|
|
194
|
-
blobClient?: BlobClientInterface;
|
|
195
187
|
p2pClientDeps?: P2PClientDeps<P2PClientType.Full>;
|
|
196
188
|
} = {},
|
|
197
189
|
options: {
|
|
@@ -271,24 +263,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
271
263
|
);
|
|
272
264
|
}
|
|
273
265
|
|
|
274
|
-
const
|
|
275
|
-
l1ChainId: config.l1ChainId,
|
|
276
|
-
rollupVersion: config.rollupVersion,
|
|
277
|
-
rollupAddress: config.l1Contracts.rollupAddress.toString(),
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const [fileStoreClients, fileStoreUploadClient] = await Promise.all([
|
|
281
|
-
createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, blobFileStoreMetadata, log),
|
|
282
|
-
createWritableFileStoreBlobClient(config.blobFileStoreUploadUrl, blobFileStoreMetadata, log),
|
|
283
|
-
]);
|
|
284
|
-
|
|
285
|
-
const blobClient =
|
|
286
|
-
deps.blobClient ??
|
|
287
|
-
createBlobClient(config, {
|
|
288
|
-
logger: createLogger('node:blob-client:client'),
|
|
289
|
-
fileStoreClients,
|
|
290
|
-
fileStoreUploadClient,
|
|
291
|
-
});
|
|
266
|
+
const blobClient = await createBlobClientWithFileStores(config, createLogger('node:blob-client:client'));
|
|
292
267
|
|
|
293
268
|
// attempt snapshot sync if possible
|
|
294
269
|
await trySnapshotSync(config, log);
|
|
@@ -334,6 +309,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
334
309
|
// We should really not be modifying the config object
|
|
335
310
|
config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
336
311
|
|
|
312
|
+
// Create BlockBuilder for EpochPruneWatcher (slasher functionality)
|
|
337
313
|
const blockBuilder = new BlockBuilder(
|
|
338
314
|
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
339
315
|
worldStateSynchronizer,
|
|
@@ -342,20 +318,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
342
318
|
telemetry,
|
|
343
319
|
);
|
|
344
320
|
|
|
321
|
+
// Create FullNodeCheckpointsBuilder for validator and non-validator block proposal handling
|
|
322
|
+
const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
|
|
323
|
+
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
324
|
+
archiver,
|
|
325
|
+
dateProvider,
|
|
326
|
+
telemetry,
|
|
327
|
+
);
|
|
328
|
+
|
|
345
329
|
// We'll accumulate sentinel watchers here
|
|
346
330
|
const watchers: Watcher[] = [];
|
|
347
331
|
|
|
348
332
|
// Create validator client if required
|
|
349
333
|
const validatorClient = createValidatorClient(config, {
|
|
334
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
335
|
+
worldState: worldStateSynchronizer,
|
|
350
336
|
p2pClient,
|
|
351
337
|
telemetry,
|
|
352
338
|
dateProvider,
|
|
353
339
|
epochCache,
|
|
354
|
-
blockBuilder,
|
|
355
340
|
blockSource: archiver,
|
|
356
341
|
l1ToL2MessageSource: archiver,
|
|
357
342
|
keyStoreManager,
|
|
358
|
-
|
|
343
|
+
blobClient,
|
|
359
344
|
});
|
|
360
345
|
|
|
361
346
|
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
@@ -373,7 +358,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
373
358
|
if (!validatorClient && config.alwaysReexecuteBlockProposals) {
|
|
374
359
|
log.info('Setting up block proposal reexecution for monitoring');
|
|
375
360
|
createBlockProposalHandler(config, {
|
|
376
|
-
|
|
361
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
362
|
+
worldState: worldStateSynchronizer,
|
|
377
363
|
epochCache,
|
|
378
364
|
blockSource: archiver,
|
|
379
365
|
l1ToL2MessageSource: archiver,
|
|
@@ -637,12 +623,24 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
637
623
|
return (await this.blockSource.getPublishedBlocks(from, limit)) ?? [];
|
|
638
624
|
}
|
|
639
625
|
|
|
626
|
+
public async getPublishedCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
|
|
627
|
+
return (await this.blockSource.getPublishedCheckpoints(from, limit)) ?? [];
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
public async getL2BlocksNew(from: BlockNumber, limit: number): Promise<L2BlockNew[]> {
|
|
631
|
+
return (await this.blockSource.getL2BlocksNew(from, limit)) ?? [];
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
public async getCheckpointedBlocks(from: BlockNumber, limit: number, proven?: boolean) {
|
|
635
|
+
return (await this.blockSource.getCheckpointedBlocks(from, limit, proven)) ?? [];
|
|
636
|
+
}
|
|
637
|
+
|
|
640
638
|
/**
|
|
641
|
-
* Method to fetch the current
|
|
642
|
-
* @returns The current
|
|
639
|
+
* Method to fetch the current min L2 fees.
|
|
640
|
+
* @returns The current min L2 fees.
|
|
643
641
|
*/
|
|
644
|
-
public async
|
|
645
|
-
return await this.globalVariableBuilder.
|
|
642
|
+
public async getCurrentMinFees(): Promise<GasFees> {
|
|
643
|
+
return await this.globalVariableBuilder.getCurrentMinFees();
|
|
646
644
|
}
|
|
647
645
|
|
|
648
646
|
public async getMaxPriorityFees(): Promise<GasFees> {
|
|
@@ -788,6 +786,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
788
786
|
this.log.info(`Stopped Aztec Node`);
|
|
789
787
|
}
|
|
790
788
|
|
|
789
|
+
/**
|
|
790
|
+
* Returns the blob client used by this node.
|
|
791
|
+
* @internal - Exposed for testing purposes only.
|
|
792
|
+
*/
|
|
793
|
+
public getBlobClient(): BlobClientInterface | undefined {
|
|
794
|
+
return this.blobClient;
|
|
795
|
+
}
|
|
796
|
+
|
|
791
797
|
/**
|
|
792
798
|
* Method to retrieve pending txs.
|
|
793
799
|
* @param limit - The number of items to returns
|
|
@@ -982,15 +988,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
982
988
|
}
|
|
983
989
|
|
|
984
990
|
/**
|
|
985
|
-
* Returns all the L2 to L1 messages in
|
|
986
|
-
* @param
|
|
987
|
-
* @returns The L2 to L1 messages (
|
|
991
|
+
* Returns all the L2 to L1 messages in an epoch.
|
|
992
|
+
* @param epoch - The epoch at which to get the data.
|
|
993
|
+
* @returns The L2 to L1 messages (empty array if the epoch is not found).
|
|
988
994
|
*/
|
|
989
|
-
public async getL2ToL1Messages(
|
|
990
|
-
|
|
991
|
-
|
|
995
|
+
public async getL2ToL1Messages(epoch: EpochNumber): Promise<Fr[][][][]> {
|
|
996
|
+
// Assumes `getBlocksForEpoch` returns blocks in ascending order of block number.
|
|
997
|
+
const blocks = await this.blockSource.getBlocksForEpoch(epoch);
|
|
998
|
+
const blocksInCheckpoints: L2Block[][] = [];
|
|
999
|
+
let previousSlotNumber = SlotNumber.ZERO;
|
|
1000
|
+
let checkpointIndex = -1;
|
|
1001
|
+
for (const block of blocks) {
|
|
1002
|
+
const slotNumber = block.header.globalVariables.slotNumber;
|
|
1003
|
+
if (slotNumber !== previousSlotNumber) {
|
|
1004
|
+
checkpointIndex++;
|
|
1005
|
+
blocksInCheckpoints.push([]);
|
|
1006
|
+
previousSlotNumber = slotNumber;
|
|
1007
|
+
}
|
|
1008
|
+
blocksInCheckpoints[checkpointIndex].push(block);
|
|
1009
|
+
}
|
|
1010
|
+
return blocksInCheckpoints.map(blocks =>
|
|
1011
|
+
blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)),
|
|
992
1012
|
);
|
|
993
|
-
return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
|
|
994
1013
|
}
|
|
995
1014
|
|
|
996
1015
|
/**
|
|
@@ -1246,7 +1265,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1246
1265
|
l1ChainId: this.l1ChainId,
|
|
1247
1266
|
rollupVersion: this.version,
|
|
1248
1267
|
setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
|
|
1249
|
-
gasFees: await this.
|
|
1268
|
+
gasFees: await this.getCurrentMinFees(),
|
|
1250
1269
|
skipFeeEnforcement,
|
|
1251
1270
|
txsPermitted: !this.config.disableTransactions,
|
|
1252
1271
|
});
|
|
@@ -1318,7 +1337,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1318
1337
|
}
|
|
1319
1338
|
|
|
1320
1339
|
// And it has an L2 block hash
|
|
1321
|
-
const l2BlockHash = await archiver.getL2Tips().then(tips => tips.
|
|
1340
|
+
const l2BlockHash = await archiver.getL2Tips().then(tips => tips.proposed.hash);
|
|
1322
1341
|
if (!l2BlockHash) {
|
|
1323
1342
|
this.metrics.recordSnapshotError();
|
|
1324
1343
|
throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
|
|
@@ -1352,7 +1371,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1352
1371
|
throw new Error('Archiver implementation does not support rollbacks.');
|
|
1353
1372
|
}
|
|
1354
1373
|
|
|
1355
|
-
const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.number);
|
|
1374
|
+
const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.block.number);
|
|
1356
1375
|
if (targetBlock < finalizedBlock) {
|
|
1357
1376
|
if (force) {
|
|
1358
1377
|
this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
|
package/src/sentinel/sentinel.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EpochCache } from '@aztec/epoch-cache';
|
|
2
|
-
import { BlockNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
2
|
+
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
3
|
import { countWhile, filterAsync, fromEntries, getEntries, mapValues } from '@aztec/foundation/collection';
|
|
4
4
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
5
5
|
import { createLogger } from '@aztec/foundation/log';
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
L2BlockStream,
|
|
20
20
|
type L2BlockStreamEvent,
|
|
21
21
|
type L2BlockStreamEventHandler,
|
|
22
|
-
|
|
22
|
+
getAttestationInfoFromPublishedCheckpoint,
|
|
23
23
|
} from '@aztec/stdlib/block';
|
|
24
24
|
import { getEpochAtSlot, getSlotRangeForEpoch, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
|
|
25
25
|
import type {
|
|
@@ -44,8 +44,10 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
44
44
|
protected initialSlot: SlotNumber | undefined;
|
|
45
45
|
protected lastProcessedSlot: SlotNumber | undefined;
|
|
46
46
|
// eslint-disable-next-line aztec-custom/no-non-primitive-in-collections
|
|
47
|
-
protected
|
|
48
|
-
|
|
47
|
+
protected slotNumberToCheckpoint: Map<
|
|
48
|
+
SlotNumber,
|
|
49
|
+
{ checkpointNumber: CheckpointNumber; archive: string; attestors: EthAddress[] }
|
|
50
|
+
> = new Map();
|
|
49
51
|
|
|
50
52
|
constructor(
|
|
51
53
|
protected epochCache: EpochCache,
|
|
@@ -87,39 +89,46 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
87
89
|
|
|
88
90
|
public async handleBlockStreamEvent(event: L2BlockStreamEvent): Promise<void> {
|
|
89
91
|
await this.l2TipsStore.handleBlockStreamEvent(event);
|
|
90
|
-
if (event.type === '
|
|
91
|
-
|
|
92
|
-
for (const block of event.blocks) {
|
|
93
|
-
this.slotNumberToBlock.set(block.block.header.getSlot(), {
|
|
94
|
-
blockNumber: BlockNumber(block.block.number),
|
|
95
|
-
archive: block.block.archive.root.toString(),
|
|
96
|
-
attestors: getAttestationInfoFromPublishedL2Block(block)
|
|
97
|
-
.filter(a => a.status === 'recovered-from-signature')
|
|
98
|
-
.map(a => a.address!),
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Prune the archive map to only keep at most N entries
|
|
103
|
-
const historyLength = this.store.getHistoryLength();
|
|
104
|
-
if (this.slotNumberToBlock.size > historyLength) {
|
|
105
|
-
const toDelete = Array.from(this.slotNumberToBlock.keys())
|
|
106
|
-
.sort((a, b) => Number(a - b))
|
|
107
|
-
.slice(0, this.slotNumberToBlock.size - historyLength);
|
|
108
|
-
for (const key of toDelete) {
|
|
109
|
-
this.slotNumberToBlock.delete(key);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
92
|
+
if (event.type === 'chain-checkpointed') {
|
|
93
|
+
this.handleCheckpoint(event);
|
|
112
94
|
} else if (event.type === 'chain-proven') {
|
|
113
95
|
await this.handleChainProven(event);
|
|
114
96
|
}
|
|
115
97
|
}
|
|
116
98
|
|
|
99
|
+
protected handleCheckpoint(event: L2BlockStreamEvent) {
|
|
100
|
+
if (event.type !== 'chain-checkpointed') {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const checkpoint = event.checkpoint;
|
|
104
|
+
|
|
105
|
+
// Store mapping from slot to archive, checkpoint number, and attestors
|
|
106
|
+
this.slotNumberToCheckpoint.set(checkpoint.checkpoint.header.slotNumber, {
|
|
107
|
+
checkpointNumber: checkpoint.checkpoint.number,
|
|
108
|
+
archive: checkpoint.checkpoint.archive.root.toString(),
|
|
109
|
+
attestors: getAttestationInfoFromPublishedCheckpoint(checkpoint)
|
|
110
|
+
.filter(a => a.status === 'recovered-from-signature')
|
|
111
|
+
.map(a => a.address!),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Prune the archive map to only keep at most N entries
|
|
115
|
+
const historyLength = this.store.getHistoryLength();
|
|
116
|
+
if (this.slotNumberToCheckpoint.size > historyLength) {
|
|
117
|
+
const toDelete = Array.from(this.slotNumberToCheckpoint.keys())
|
|
118
|
+
.sort((a, b) => Number(a - b))
|
|
119
|
+
.slice(0, this.slotNumberToCheckpoint.size - historyLength);
|
|
120
|
+
for (const key of toDelete) {
|
|
121
|
+
this.slotNumberToCheckpoint.delete(key);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
117
126
|
protected async handleChainProven(event: L2BlockStreamEvent) {
|
|
118
127
|
if (event.type !== 'chain-proven') {
|
|
119
128
|
return;
|
|
120
129
|
}
|
|
121
|
-
const blockNumber =
|
|
122
|
-
const block = await this.archiver.
|
|
130
|
+
const blockNumber = event.block.number;
|
|
131
|
+
const block = await this.archiver.getL2BlockNew(blockNumber);
|
|
123
132
|
if (!block) {
|
|
124
133
|
this.logger.error(`Failed to get block ${blockNumber}`, { block });
|
|
125
134
|
return;
|
|
@@ -291,8 +300,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
291
300
|
return false;
|
|
292
301
|
}
|
|
293
302
|
|
|
294
|
-
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then(tip => tip.
|
|
295
|
-
const p2pLastBlockHash = await this.p2p.getL2Tips().then(tips => tips.
|
|
303
|
+
const archiverLastBlockHash = await this.l2TipsStore.getL2Tips().then(tip => tip.proposed.hash);
|
|
304
|
+
const p2pLastBlockHash = await this.p2p.getL2Tips().then(tips => tips.proposed.hash);
|
|
296
305
|
const isP2pSynced = archiverLastBlockHash === p2pLastBlockHash;
|
|
297
306
|
if (!isP2pSynced) {
|
|
298
307
|
this.logger.debug(`Waiting for P2P client to sync with archiver`, { archiverLastBlockHash, p2pLastBlockHash });
|
|
@@ -331,8 +340,8 @@ export class Sentinel extends (EventEmitter as new () => WatcherEmitter) impleme
|
|
|
331
340
|
// or all attestations for all proposals in the slot if no block was mined.
|
|
332
341
|
// We gather from both p2p (contains the ones seen on the p2p layer) and archiver
|
|
333
342
|
// (contains the ones synced from mined blocks, which we may have missed from p2p).
|
|
334
|
-
const block = this.
|
|
335
|
-
const p2pAttested = await this.p2p.
|
|
343
|
+
const block = this.slotNumberToCheckpoint.get(slot);
|
|
344
|
+
const p2pAttested = await this.p2p.getCheckpointAttestationsForSlot(slot, block?.archive);
|
|
336
345
|
// Filter out attestations with invalid signatures
|
|
337
346
|
const p2pAttestors = p2pAttested.map(a => a.getSender()).filter((s): s is EthAddress => s !== undefined);
|
|
338
347
|
const attestors = new Set(
|