@lodestar/beacon-node 1.51.0-dev.3ff306e42b → 1.51.0-dev.41b1b87394
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/lib/chain/errors/executionPayloadEnvelope.d.ts +7 -0
- package/lib/chain/errors/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/errors/executionPayloadEnvelope.js +1 -0
- package/lib/chain/errors/executionPayloadEnvelope.js.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.d.ts +6 -0
- package/lib/chain/validation/executionPayloadEnvelope.d.ts.map +1 -1
- package/lib/chain/validation/executionPayloadEnvelope.js +53 -0
- package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
- package/lib/execution/engine/utils.d.ts.map +1 -1
- package/lib/execution/engine/utils.js +3 -2
- package/lib/execution/engine/utils.js.map +1 -1
- package/lib/network/gossip/gossipsub.d.ts +1 -0
- package/lib/network/gossip/gossipsub.d.ts.map +1 -1
- package/lib/network/gossip/gossipsub.js +10 -0
- package/lib/network/gossip/gossipsub.js.map +1 -1
- package/lib/network/network.d.ts +2 -1
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +42 -17
- package/lib/network/network.js.map +1 -1
- package/lib/network/peers/peerManager.d.ts.map +1 -1
- package/lib/network/peers/peerManager.js +28 -1
- package/lib/network/peers/peerManager.js.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts +4 -1
- package/lib/network/peers/utils/prioritizePeers.d.ts.map +1 -1
- package/lib/network/peers/utils/prioritizePeers.js +8 -16
- package/lib/network/peers/utils/prioritizePeers.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +21 -11
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/index.d.ts +6 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +29 -3
- package/lib/network/processor/index.js.map +1 -1
- package/lib/node/nodejs.d.ts +2 -0
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +36 -19
- package/lib/node/nodejs.js.map +1 -1
- package/lib/util/sszBytes.d.ts +5 -0
- package/lib/util/sszBytes.d.ts.map +1 -1
- package/lib/util/sszBytes.js +33 -0
- package/lib/util/sszBytes.js.map +1 -1
- package/package.json +16 -16
- package/src/chain/errors/executionPayloadEnvelope.ts +8 -0
- package/src/chain/validation/executionPayloadEnvelope.ts +68 -1
- package/src/execution/engine/utils.ts +4 -2
- package/src/network/gossip/gossipsub.ts +11 -0
- package/src/network/network.ts +40 -21
- package/src/network/peers/peerManager.ts +27 -2
- package/src/network/peers/utils/prioritizePeers.ts +14 -22
- package/src/network/processor/gossipHandlers.ts +26 -13
- package/src/network/processor/index.ts +25 -3
- package/src/node/nodejs.ts +33 -15
- package/src/util/sszBytes.ts +40 -0
|
@@ -49,6 +49,8 @@ const syncnetsZero = BitArray.fromBitLen(SYNC_COMMITTEE_SUBNET_COUNT);
|
|
|
49
49
|
|
|
50
50
|
type SubnetDiscvQuery = {subnet: SubnetID; toSlot: number; maxPeersToDiscover: number};
|
|
51
51
|
|
|
52
|
+
export type RequestedSubnetWithPeerCount = RequestedSubnet & {peerCount: number};
|
|
53
|
+
|
|
52
54
|
/**
|
|
53
55
|
* A map of das custody group index to maxPeersToDiscover
|
|
54
56
|
*/
|
|
@@ -145,8 +147,8 @@ export function prioritizePeers(
|
|
|
145
147
|
samplingGroups: CustodyIndex[] | null;
|
|
146
148
|
score: number;
|
|
147
149
|
}[],
|
|
148
|
-
activeAttnets:
|
|
149
|
-
activeSyncnets:
|
|
150
|
+
activeAttnets: RequestedSubnetWithPeerCount[],
|
|
151
|
+
activeSyncnets: RequestedSubnetWithPeerCount[],
|
|
150
152
|
samplingGroups: CustodyIndex[] | undefined,
|
|
151
153
|
opts: PrioritizePeersOpts,
|
|
152
154
|
config: ChainConfig,
|
|
@@ -231,8 +233,8 @@ export function prioritizePeers(
|
|
|
231
233
|
*/
|
|
232
234
|
function requestSubnetPeers(
|
|
233
235
|
connectedPeers: PeerInfo[],
|
|
234
|
-
activeAttnets:
|
|
235
|
-
activeSyncnets:
|
|
236
|
+
activeAttnets: RequestedSubnetWithPeerCount[],
|
|
237
|
+
activeSyncnets: RequestedSubnetWithPeerCount[],
|
|
236
238
|
ourSamplingGroups: CustodyIndex[] | undefined,
|
|
237
239
|
opts: PrioritizePeersOpts,
|
|
238
240
|
config: ChainConfig,
|
|
@@ -252,54 +254,44 @@ function requestSubnetPeers(
|
|
|
252
254
|
|
|
253
255
|
// attnets, do we need queries for more peers
|
|
254
256
|
if (activeAttnets.length > 0) {
|
|
255
|
-
/** Map of peers per subnet, peer may be in multiple arrays */
|
|
256
|
-
const peersPerSubnet = new Map<number, number>();
|
|
257
|
-
|
|
258
257
|
for (const peer of connectedPeers) {
|
|
259
258
|
const trueBitIndices = peer.attnetsTrueBitIndices;
|
|
260
259
|
let dutyCount = 0;
|
|
261
260
|
for (const {subnet} of activeAttnets) {
|
|
262
261
|
if (trueBitIndices.includes(subnet)) {
|
|
263
262
|
dutyCount += 1;
|
|
264
|
-
peersPerSubnet.set(subnet, 1 + (peersPerSubnet.get(subnet) ?? 0));
|
|
265
263
|
}
|
|
266
264
|
}
|
|
267
265
|
dutiesByPeer.set(peer, dutyCount);
|
|
268
266
|
}
|
|
269
267
|
|
|
270
|
-
for (const {subnet, toSlot} of activeAttnets) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (peersInSubnet < targetSubnetPeers) {
|
|
268
|
+
for (const {subnet, toSlot, peerCount} of activeAttnets) {
|
|
269
|
+
metrics?.peerManager.peersPerActiveSubnet.observe({type: SubnetType.attnets}, peerCount);
|
|
270
|
+
if (peerCount < targetSubnetPeers) {
|
|
274
271
|
// We need more peers
|
|
275
|
-
attnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers -
|
|
272
|
+
attnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers - peerCount});
|
|
276
273
|
}
|
|
277
274
|
}
|
|
278
275
|
}
|
|
279
276
|
|
|
280
277
|
// syncnets, do we need queries for more peers
|
|
281
278
|
if (activeSyncnets.length > 0) {
|
|
282
|
-
/** Map of peers per subnet, peer may be in multiple arrays */
|
|
283
|
-
const peersPerSubnet = new Map<number, number>();
|
|
284
|
-
|
|
285
279
|
for (const peer of connectedPeers) {
|
|
286
280
|
const trueBitIndices = peer.syncnetsTrueBitIndices;
|
|
287
281
|
let dutyCount = dutiesByPeer.get(peer) ?? 0;
|
|
288
282
|
for (const {subnet} of activeSyncnets) {
|
|
289
283
|
if (trueBitIndices.includes(subnet)) {
|
|
290
284
|
dutyCount += 1;
|
|
291
|
-
peersPerSubnet.set(subnet, 1 + (peersPerSubnet.get(subnet) ?? 0));
|
|
292
285
|
}
|
|
293
286
|
}
|
|
294
287
|
dutiesByPeer.set(peer, dutyCount);
|
|
295
288
|
}
|
|
296
289
|
|
|
297
|
-
for (const {subnet, toSlot} of activeSyncnets) {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if (peersInSubnet < targetSubnetPeers) {
|
|
290
|
+
for (const {subnet, toSlot, peerCount} of activeSyncnets) {
|
|
291
|
+
metrics?.peerManager.peersPerActiveSubnet.observe({type: SubnetType.syncnets}, peerCount);
|
|
292
|
+
if (peerCount < targetSubnetPeers) {
|
|
301
293
|
// We need more peers
|
|
302
|
-
syncnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers -
|
|
294
|
+
syncnetQueries.push({subnet, toSlot, maxPeersToDiscover: targetSubnetPeers - peerCount});
|
|
303
295
|
}
|
|
304
296
|
}
|
|
305
297
|
}
|
|
@@ -66,7 +66,10 @@ import {
|
|
|
66
66
|
validateGossipGloasDataColumnSidecar,
|
|
67
67
|
} from "../../chain/validation/dataColumnSidecar.js";
|
|
68
68
|
import {validateGossipExecutionPayloadBid} from "../../chain/validation/executionPayloadBid.js";
|
|
69
|
-
import {
|
|
69
|
+
import {
|
|
70
|
+
validateGossipExecutionPayloadEnvelope,
|
|
71
|
+
validateGossipExecutionPayloadEnvelopeSerialized,
|
|
72
|
+
} from "../../chain/validation/executionPayloadEnvelope.js";
|
|
70
73
|
import {InclusionListValidationResult, validateGossipInclusionList} from "../../chain/validation/inclusionList.js";
|
|
71
74
|
import {
|
|
72
75
|
AggregateAndProofValidationResult,
|
|
@@ -1276,6 +1279,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1276
1279
|
seenTimestampSec,
|
|
1277
1280
|
}: GossipHandlerParamGeneric<GossipType.execution_payload>) => {
|
|
1278
1281
|
const {serializedData} = gossipData;
|
|
1282
|
+
validateGossipExecutionPayloadEnvelopeSerialized(chain, serializedData);
|
|
1279
1283
|
const signedEnvelope = sszDeserialize(topic, serializedData);
|
|
1280
1284
|
const envelope = signedEnvelope.message;
|
|
1281
1285
|
|
|
@@ -1328,18 +1332,22 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1328
1332
|
});
|
|
1329
1333
|
}
|
|
1330
1334
|
|
|
1335
|
+
// Add the envelope in the same tick as validation, otherwise a by-root download that completes
|
|
1336
|
+
// before the next event loop sees no envelope, validates it again and adds it first
|
|
1337
|
+
if (!payloadInput.hasPayloadEnvelope()) {
|
|
1338
|
+
chain.serializedCache.set(signedEnvelope, serializedData);
|
|
1339
|
+
|
|
1340
|
+
payloadInput.addPayloadEnvelope({
|
|
1341
|
+
envelope: signedEnvelope,
|
|
1342
|
+
source: PayloadEnvelopeInputSource.gossip,
|
|
1343
|
+
seenTimestampSec,
|
|
1344
|
+
peerIdStr,
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1331
1348
|
// Handler - deferred to next event loop so the validation result propagates first
|
|
1332
1349
|
callInNextEventLoop(() => {
|
|
1333
1350
|
try {
|
|
1334
|
-
chain.serializedCache.set(signedEnvelope, serializedData);
|
|
1335
|
-
|
|
1336
|
-
payloadInput.addPayloadEnvelope({
|
|
1337
|
-
envelope: signedEnvelope,
|
|
1338
|
-
source: PayloadEnvelopeInputSource.gossip,
|
|
1339
|
-
seenTimestampSec,
|
|
1340
|
-
peerIdStr,
|
|
1341
|
-
});
|
|
1342
|
-
|
|
1343
1351
|
scheduleIncompletePayloadEnvelope(payloadInput, peerIdStr);
|
|
1344
1352
|
|
|
1345
1353
|
chain.emitter.emit(routes.events.EventType.executionPayloadGossip, {
|
|
@@ -1349,7 +1357,9 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1349
1357
|
blockRoot: blockRootHex,
|
|
1350
1358
|
});
|
|
1351
1359
|
|
|
1352
|
-
|
|
1360
|
+
// Signature is verified above only for this envelope, one added by another source is verified at import
|
|
1361
|
+
const validSignature = payloadInput.getPayloadEnvelope() === signedEnvelope;
|
|
1362
|
+
chain.processExecutionPayload(payloadInput, {validSignature}).catch((e) => {
|
|
1353
1363
|
// Adjust verbosity based on error type
|
|
1354
1364
|
let logLevel: LogLevel;
|
|
1355
1365
|
|
|
@@ -1367,11 +1377,14 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
1367
1377
|
// Errors might indicate an issue with our node or the connected EL client
|
|
1368
1378
|
logLevel = LogLevel.error;
|
|
1369
1379
|
break;
|
|
1370
|
-
// INVALID_SIGNATURE
|
|
1380
|
+
// INVALID_SIGNATURE only happens for an envelope added by another source
|
|
1371
1381
|
case PayloadErrorCode.INVALID_SIGNATURE:
|
|
1372
1382
|
case PayloadErrorCode.ENVELOPE_VERIFICATION_ERROR:
|
|
1373
1383
|
case PayloadErrorCode.EXECUTION_ENGINE_INVALID:
|
|
1374
|
-
|
|
1384
|
+
// Penalize only when the stored envelope is the one this peer sent, not one added by another source
|
|
1385
|
+
if (validSignature) {
|
|
1386
|
+
core.reportPeer(peerIdStr, PeerAction.LowToleranceError, "BadGossipPayload");
|
|
1387
|
+
}
|
|
1375
1388
|
// The builder may have signed another envelope that is valid, keeping this one would make
|
|
1376
1389
|
// by-root and range sync reuse it and never import the payload
|
|
1377
1390
|
chain.seenPayloadEnvelopeInputCache.removeInvalid(payloadInput);
|
|
@@ -244,6 +244,12 @@ type SearchUnknownRootTarget =
|
|
|
244
244
|
* Such that enough work is processed to fill either one of the queue.
|
|
245
245
|
*/
|
|
246
246
|
export class NetworkProcessor {
|
|
247
|
+
private stopped = false;
|
|
248
|
+
/**
|
|
249
|
+
* Replaced by dropAllJobs(). Loops that yield while re-queueing awaiting messages compare it after each yield
|
|
250
|
+
* and stop, so no message is queued after a drop.
|
|
251
|
+
*/
|
|
252
|
+
private jobGeneration = Symbol();
|
|
247
253
|
private readonly chain: IBeaconChain;
|
|
248
254
|
private readonly core: INetworkCore;
|
|
249
255
|
private readonly events: NetworkEventBus;
|
|
@@ -335,16 +341,27 @@ export class NetworkProcessor {
|
|
|
335
341
|
}
|
|
336
342
|
|
|
337
343
|
async stop(): Promise<void> {
|
|
344
|
+
if (this.stopped) return;
|
|
345
|
+
this.stopped = true;
|
|
338
346
|
this.events.off(NetworkEvent.pendingGossipsubMessage, this.onPendingGossipsubMessage);
|
|
339
347
|
this.chain.emitter.off(routes.events.EventType.block, this.onBlockProcessed);
|
|
340
348
|
this.chain.emitter.off(routes.events.EventType.executionPayload, this.onPayloadEnvelopeProcessed);
|
|
341
|
-
this.chain.
|
|
349
|
+
this.chain.clock.off(ClockEvent.slot, this.onClockSlot);
|
|
350
|
+
this.dropAllJobs();
|
|
342
351
|
}
|
|
343
352
|
|
|
344
353
|
dropAllJobs(): void {
|
|
354
|
+
this.jobGeneration = Symbol();
|
|
345
355
|
for (const topic of executeGossipWorkOrder) {
|
|
346
356
|
this.gossipQueues[topic].clear();
|
|
347
357
|
}
|
|
358
|
+
this.awaitingMessagesByBlockRoot.clear();
|
|
359
|
+
this.awaitingBlockMessageCount = 0;
|
|
360
|
+
this.awaitingMessagesByPayloadBlockRoot.clear();
|
|
361
|
+
this.awaitingPayloadMessageCount = 0;
|
|
362
|
+
this.searchedRootsBySlot.clear();
|
|
363
|
+
this.bufferedRootsBySlot.clear();
|
|
364
|
+
this.fatalPeers.clear();
|
|
348
365
|
}
|
|
349
366
|
|
|
350
367
|
dumpGossipQueue(topic: GossipType): PendingGossipsubMessage[] {
|
|
@@ -364,7 +381,7 @@ export class NetworkProcessor {
|
|
|
364
381
|
* prefer them as by-root download candidates.
|
|
365
382
|
*/
|
|
366
383
|
searchUnknownBlock({slot, root}: SlotRootHex, source: BlockInputSource, peer?: PeerIdStr): void {
|
|
367
|
-
if (this.chain.seenBlock(root)) {
|
|
384
|
+
if (this.stopped || this.chain.seenBlock(root)) {
|
|
368
385
|
return;
|
|
369
386
|
}
|
|
370
387
|
const entry = this.searchedRootsBySlot.getOrDefault(slot).getOrDefault(root);
|
|
@@ -398,7 +415,7 @@ export class NetworkProcessor {
|
|
|
398
415
|
peer?: PeerIdStr,
|
|
399
416
|
slotIsPayloadSlot = false
|
|
400
417
|
): void {
|
|
401
|
-
if (this.chain.seenPayloadEnvelope(root)) {
|
|
418
|
+
if (this.stopped || this.chain.seenPayloadEnvelope(root)) {
|
|
402
419
|
return;
|
|
403
420
|
}
|
|
404
421
|
const entry = this.searchedRootsBySlot.getOrDefault(slot).getOrDefault(root);
|
|
@@ -801,6 +818,7 @@ export class NetworkProcessor {
|
|
|
801
818
|
this.awaitingBlockMessageCount -= total;
|
|
802
819
|
}
|
|
803
820
|
|
|
821
|
+
const generation = this.jobGeneration;
|
|
804
822
|
const nowSec = Date.now() / 1000;
|
|
805
823
|
let count = 0;
|
|
806
824
|
// TODO: we can group attestations to process in batches but since we have the SeenAttestationDatas
|
|
@@ -819,6 +837,7 @@ export class NetworkProcessor {
|
|
|
819
837
|
if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
|
|
820
838
|
count = 0;
|
|
821
839
|
await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
|
|
840
|
+
if (generation !== this.jobGeneration) return;
|
|
822
841
|
}
|
|
823
842
|
}
|
|
824
843
|
}
|
|
@@ -843,6 +862,7 @@ export class NetworkProcessor {
|
|
|
843
862
|
this.awaitingPayloadMessageCount -= total;
|
|
844
863
|
}
|
|
845
864
|
|
|
865
|
+
const generation = this.jobGeneration;
|
|
846
866
|
const nowSec = Date.now() / 1000;
|
|
847
867
|
let count = 0;
|
|
848
868
|
for (const messages of messagesByTopic.values()) {
|
|
@@ -858,6 +878,7 @@ export class NetworkProcessor {
|
|
|
858
878
|
if (count === MAX_AWAITING_GOSSIP_OBJECTS_PER_TICK) {
|
|
859
879
|
count = 0;
|
|
860
880
|
await sleep(AWAITING_GOSSIP_OBJECTS_YIELD_EVERY_MS);
|
|
881
|
+
if (generation !== this.jobGeneration) return;
|
|
861
882
|
}
|
|
862
883
|
}
|
|
863
884
|
}
|
|
@@ -937,6 +958,7 @@ export class NetworkProcessor {
|
|
|
937
958
|
};
|
|
938
959
|
|
|
939
960
|
private executeWork(): void {
|
|
961
|
+
if (this.stopped) return;
|
|
940
962
|
// TODO: Maybe de-bounce by timing the last time executeWork was run
|
|
941
963
|
|
|
942
964
|
this.metrics?.networkProcessor.executeWorkCalls.inc();
|
package/src/node/nodejs.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
isStatePostGloas,
|
|
21
21
|
} from "@lodestar/state-transition";
|
|
22
22
|
import {Slot} from "@lodestar/types";
|
|
23
|
-
import {sleep, toRootHex} from "@lodestar/utils";
|
|
23
|
+
import {defer, sleep, toRootHex} from "@lodestar/utils";
|
|
24
24
|
import {ProcessShutdownCallback} from "@lodestar/validator";
|
|
25
25
|
import {BeaconRestApiServer, getApi} from "../api/index.js";
|
|
26
26
|
import {getDataColumnSidecarsMinEpoch} from "../chain/archiveStore/utils/archiveBlocks.js";
|
|
@@ -117,6 +117,7 @@ export class BeaconNode {
|
|
|
117
117
|
|
|
118
118
|
status: BeaconNodeStatus;
|
|
119
119
|
private controller?: AbortController;
|
|
120
|
+
private closePromise: Promise<void> | undefined;
|
|
120
121
|
|
|
121
122
|
constructor({
|
|
122
123
|
opts,
|
|
@@ -393,23 +394,40 @@ export class BeaconNode {
|
|
|
393
394
|
/**
|
|
394
395
|
* Stop beacon node and its sub-components.
|
|
395
396
|
*/
|
|
396
|
-
|
|
397
|
-
if (this.
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
397
|
+
close(): Promise<void> {
|
|
398
|
+
if (this.closePromise !== undefined) return this.closePromise;
|
|
399
|
+
const completion = defer<void>();
|
|
400
|
+
this.closePromise = completion.promise;
|
|
401
|
+
this.status = BeaconNodeStatus.closing;
|
|
402
|
+
void this.closeInternal().then(completion.resolve, completion.reject);
|
|
403
|
+
return this.closePromise;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private async closeInternal(): Promise<void> {
|
|
407
|
+
const errors: unknown[] = [];
|
|
408
|
+
for (const cleanup of [
|
|
409
|
+
() => this.sync.close(),
|
|
410
|
+
() => this.restApi?.close(),
|
|
411
|
+
() => this.network.close(),
|
|
412
|
+
() => this.metricsServer?.close(),
|
|
413
|
+
() => this.monitoring?.close(),
|
|
414
|
+
() => this.chain.persistToDisk(),
|
|
415
|
+
() => this.chain.close(),
|
|
406
416
|
// Abort signal last: close() calls above clear intervals/timeouts so no new
|
|
407
417
|
// operations get scheduled. If we aborted first, a still-pending interval could
|
|
408
418
|
// fire and schedule a new operation after abort, leaving it stuck and delaying shutdown.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
419
|
+
() => this.controller?.abort(),
|
|
420
|
+
() => sleep(DELAY_BEFORE_CLOSING_DB_MS),
|
|
421
|
+
() => this.db.close(),
|
|
422
|
+
]) {
|
|
423
|
+
try {
|
|
424
|
+
await cleanup();
|
|
425
|
+
} catch (error) {
|
|
426
|
+
errors.push(error);
|
|
427
|
+
}
|
|
413
428
|
}
|
|
429
|
+
this.status = BeaconNodeStatus.closed;
|
|
430
|
+
if (errors.length === 1) throw errors[0];
|
|
431
|
+
if (errors.length > 1) throw new AggregateError(errors, "Beacon node cleanup failed");
|
|
414
432
|
}
|
|
415
433
|
}
|
package/src/util/sszBytes.ts
CHANGED
|
@@ -652,6 +652,46 @@ export function getBeaconBlockRootFromExecutionPayloadEnvelopeSerialized(data: U
|
|
|
652
652
|
return "0x" + blockRootBuf.toString("hex");
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
+
// transactions offset within ExecutionPayload fixed portion, see SLOT_NUMBER_OFFSET_IN_EXECUTION_PAYLOAD:
|
|
656
|
+
// everything up to and including blockHash(32) = 504, followed by withdrawals_offset(4)
|
|
657
|
+
const PAYLOAD_START_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE =
|
|
658
|
+
ENVELOPE_START_IN_SIGNED + EXECUTION_PAYLOAD_ENVELOPE_FIXED_SIZE; // 180
|
|
659
|
+
const TRANSACTIONS_OFFSET_POSITION_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE =
|
|
660
|
+
PAYLOAD_START_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE + 504; // 684
|
|
661
|
+
const WITHDRAWALS_OFFSET_POSITION_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE =
|
|
662
|
+
TRANSACTIONS_OFFSET_POSITION_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE + 4; // 688
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* Transaction count of the payload, read from the SSZ offsets without deserializing the envelope.
|
|
666
|
+
* Returns null on malformed offsets, deserialization reports the exact error.
|
|
667
|
+
*/
|
|
668
|
+
export function getTransactionCountFromExecutionPayloadEnvelopeSerialized(data: Uint8Array): number | null {
|
|
669
|
+
if (data.length < WITHDRAWALS_OFFSET_POSITION_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE + 4) {
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
674
|
+
const transactionsOffset = view.getUint32(TRANSACTIONS_OFFSET_POSITION_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE, true);
|
|
675
|
+
const withdrawalsOffset = view.getUint32(WITHDRAWALS_OFFSET_POSITION_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE, true);
|
|
676
|
+
if (transactionsOffset > withdrawalsOffset) {
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
if (transactionsOffset === withdrawalsOffset) {
|
|
680
|
+
return 0;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const transactionsStart = PAYLOAD_START_IN_SIGNED_EXECUTION_PAYLOAD_ENVELOPE + transactionsOffset;
|
|
684
|
+
if (transactionsStart + 4 > data.length) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
// first offset of a non-empty list is the size of its offset table, 4 bytes per element
|
|
688
|
+
const firstTransactionOffset = view.getUint32(transactionsStart, true);
|
|
689
|
+
if (firstTransactionOffset % 4 !== 0 || firstTransactionOffset > withdrawalsOffset - transactionsOffset) {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
return firstTransactionOffset / 4;
|
|
693
|
+
}
|
|
694
|
+
|
|
655
695
|
/**
|
|
656
696
|
* BeaconState of all forks (up until Electra, check with new forks)
|
|
657
697
|
* class BeaconState(Container):
|