@lodestar/beacon-node 1.40.0-dev.db4220e83d → 1.40.0-dev.f58ab9b042

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.
Files changed (85) hide show
  1. package/lib/api/impl/lodestar/index.d.ts.map +1 -1
  2. package/lib/api/impl/lodestar/index.js +14 -0
  3. package/lib/api/impl/lodestar/index.js.map +1 -1
  4. package/lib/api/rest/base.d.ts.map +1 -1
  5. package/lib/api/rest/base.js +12 -10
  6. package/lib/api/rest/base.js.map +1 -1
  7. package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
  8. package/lib/chain/archiveStore/historicalState/getHistoricalState.js +2 -1
  9. package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
  10. package/lib/chain/blocks/blockInput/blockInput.js +2 -2
  11. package/lib/chain/blocks/blockInput/blockInput.js.map +1 -1
  12. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  13. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +1 -2
  14. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  15. package/lib/chain/initState.d.ts.map +1 -1
  16. package/lib/chain/initState.js +2 -2
  17. package/lib/chain/initState.js.map +1 -1
  18. package/lib/chain/lightClient/index.d.ts.map +1 -1
  19. package/lib/chain/lightClient/index.js +1 -2
  20. package/lib/chain/lightClient/index.js.map +1 -1
  21. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  22. package/lib/chain/seenCache/seenGossipBlockInput.js +2 -2
  23. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  24. package/lib/chain/serializeState.d.ts.map +1 -1
  25. package/lib/chain/serializeState.js +2 -1
  26. package/lib/chain/serializeState.js.map +1 -1
  27. package/lib/chain/validation/blobSidecar.js +2 -2
  28. package/lib/chain/validation/blobSidecar.js.map +1 -1
  29. package/lib/chain/validation/dataColumnSidecar.js +2 -2
  30. package/lib/chain/validation/dataColumnSidecar.js.map +1 -1
  31. package/lib/network/core/networkCore.d.ts +3 -0
  32. package/lib/network/core/networkCore.d.ts.map +1 -1
  33. package/lib/network/core/networkCore.js +9 -0
  34. package/lib/network/core/networkCore.js.map +1 -1
  35. package/lib/network/core/networkCoreWorker.js +3 -0
  36. package/lib/network/core/networkCoreWorker.js.map +1 -1
  37. package/lib/network/core/networkCoreWorkerHandler.d.ts +3 -0
  38. package/lib/network/core/networkCoreWorkerHandler.d.ts.map +1 -1
  39. package/lib/network/core/networkCoreWorkerHandler.js +9 -0
  40. package/lib/network/core/networkCoreWorkerHandler.js.map +1 -1
  41. package/lib/network/core/types.d.ts +3 -0
  42. package/lib/network/core/types.d.ts.map +1 -1
  43. package/lib/network/gossip/gossipsub.d.ts +16 -1
  44. package/lib/network/gossip/gossipsub.d.ts.map +1 -1
  45. package/lib/network/gossip/gossipsub.js +52 -0
  46. package/lib/network/gossip/gossipsub.js.map +1 -1
  47. package/lib/network/network.d.ts +3 -0
  48. package/lib/network/network.d.ts.map +1 -1
  49. package/lib/network/network.js +9 -0
  50. package/lib/network/network.js.map +1 -1
  51. package/lib/sync/backfill/backfill.d.ts.map +1 -1
  52. package/lib/sync/backfill/backfill.js +1 -2
  53. package/lib/sync/backfill/backfill.js.map +1 -1
  54. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  55. package/lib/sync/utils/downloadByRange.js +2 -2
  56. package/lib/sync/utils/downloadByRange.js.map +1 -1
  57. package/lib/sync/utils/downloadByRoot.d.ts.map +1 -1
  58. package/lib/sync/utils/downloadByRoot.js +1 -2
  59. package/lib/sync/utils/downloadByRoot.js.map +1 -1
  60. package/package.json +16 -16
  61. package/src/api/impl/lodestar/index.ts +17 -0
  62. package/src/api/rest/base.ts +15 -13
  63. package/src/chain/archiveStore/historicalState/getHistoricalState.ts +2 -1
  64. package/src/chain/blocks/blockInput/blockInput.ts +2 -2
  65. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +1 -2
  66. package/src/chain/initState.ts +2 -2
  67. package/src/chain/lightClient/index.ts +1 -2
  68. package/src/chain/seenCache/seenGossipBlockInput.ts +2 -2
  69. package/src/chain/serializeState.ts +2 -1
  70. package/src/chain/validation/blobSidecar.ts +2 -2
  71. package/src/chain/validation/dataColumnSidecar.ts +2 -2
  72. package/src/network/core/networkCore.ts +12 -0
  73. package/src/network/core/networkCoreWorker.ts +3 -0
  74. package/src/network/core/networkCoreWorkerHandler.ts +9 -0
  75. package/src/network/core/types.ts +6 -0
  76. package/src/network/gossip/gossipsub.ts +62 -1
  77. package/src/network/network.ts +12 -0
  78. package/src/sync/backfill/backfill.ts +1 -2
  79. package/src/sync/utils/downloadByRange.ts +2 -2
  80. package/src/sync/utils/downloadByRoot.ts +1 -2
  81. package/lib/util/bytes.d.ts +0 -3
  82. package/lib/util/bytes.d.ts.map +0 -1
  83. package/lib/util/bytes.js +0 -11
  84. package/lib/util/bytes.js.map +0 -1
  85. package/src/util/bytes.ts +0 -11
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  "bugs": {
12
12
  "url": "https://github.com/ChainSafe/lodestar/issues"
13
13
  },
14
- "version": "1.40.0-dev.db4220e83d",
14
+ "version": "1.40.0-dev.f58ab9b042",
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": {
@@ -134,24 +134,24 @@
134
134
  "@libp2p/peer-id": "^5.1.0",
135
135
  "@libp2p/prometheus-metrics": "^4.3.15",
136
136
  "@libp2p/tcp": "^10.1.8",
137
- "@lodestar/api": "^1.40.0-dev.db4220e83d",
138
- "@lodestar/config": "^1.40.0-dev.db4220e83d",
139
- "@lodestar/db": "^1.40.0-dev.db4220e83d",
140
- "@lodestar/fork-choice": "^1.40.0-dev.db4220e83d",
141
- "@lodestar/light-client": "^1.40.0-dev.db4220e83d",
142
- "@lodestar/logger": "^1.40.0-dev.db4220e83d",
143
- "@lodestar/params": "^1.40.0-dev.db4220e83d",
144
- "@lodestar/reqresp": "^1.40.0-dev.db4220e83d",
145
- "@lodestar/state-transition": "^1.40.0-dev.db4220e83d",
146
- "@lodestar/types": "^1.40.0-dev.db4220e83d",
147
- "@lodestar/utils": "^1.40.0-dev.db4220e83d",
148
- "@lodestar/validator": "^1.40.0-dev.db4220e83d",
137
+ "@lodestar/api": "^1.40.0-dev.f58ab9b042",
138
+ "@lodestar/config": "^1.40.0-dev.f58ab9b042",
139
+ "@lodestar/db": "^1.40.0-dev.f58ab9b042",
140
+ "@lodestar/fork-choice": "^1.40.0-dev.f58ab9b042",
141
+ "@lodestar/light-client": "^1.40.0-dev.f58ab9b042",
142
+ "@lodestar/logger": "^1.40.0-dev.f58ab9b042",
143
+ "@lodestar/params": "^1.40.0-dev.f58ab9b042",
144
+ "@lodestar/reqresp": "^1.40.0-dev.f58ab9b042",
145
+ "@lodestar/state-transition": "^1.40.0-dev.f58ab9b042",
146
+ "@lodestar/types": "^1.40.0-dev.f58ab9b042",
147
+ "@lodestar/utils": "^1.40.0-dev.f58ab9b042",
148
+ "@lodestar/validator": "^1.40.0-dev.f58ab9b042",
149
149
  "@multiformats/multiaddr": "^12.1.3",
150
150
  "datastore-core": "^10.0.2",
151
151
  "datastore-fs": "^10.0.6",
152
152
  "datastore-level": "^11.0.3",
153
153
  "deepmerge": "^4.3.1",
154
- "fastify": "^5.2.1",
154
+ "fastify": "^5.7.4",
155
155
  "interface-datastore": "^8.3.0",
156
156
  "it-all": "^3.0.4",
157
157
  "it-pipe": "^3.0.1",
@@ -169,7 +169,7 @@
169
169
  "@chainsafe/swap-or-not-shuffle": "^1.2.1",
170
170
  "@libp2p/interface-internal": "^2.3.18",
171
171
  "@libp2p/logger": "^5.1.21",
172
- "@lodestar/spec-test-util": "^1.40.0-dev.db4220e83d",
172
+ "@lodestar/spec-test-util": "^1.40.0-dev.f58ab9b042",
173
173
  "@types/js-yaml": "^4.0.5",
174
174
  "@types/qs": "^6.9.7",
175
175
  "@types/tmp": "^0.2.3",
@@ -188,5 +188,5 @@
188
188
  "beacon",
189
189
  "blockchain"
190
190
  ],
191
- "gitHead": "bbabc495c920b4d1a89771d3da36d85c4632514e"
191
+ "gitHead": "e8c409a006f8264d0dbd6f5c2479b9d364098ebd"
192
192
  }
@@ -154,6 +154,23 @@ export function getLodestarApi({
154
154
  await network.disconnectPeer(peerId);
155
155
  },
156
156
 
157
+ async addDirectPeer({peer}) {
158
+ const peerId = await network.addDirectPeer(peer);
159
+ if (peerId === null) {
160
+ throw new ApiError(400, `Failed to add direct peer: invalid peer address or ENR "${peer}"`);
161
+ }
162
+ return {data: {peerId}};
163
+ },
164
+
165
+ async removeDirectPeer({peerId}) {
166
+ const removed = await network.removeDirectPeer(peerId);
167
+ return {data: {removed}};
168
+ },
169
+
170
+ async getDirectPeers() {
171
+ return {data: await network.getDirectPeers()};
172
+ },
173
+
157
174
  async getPeers({state, direction}) {
158
175
  const peers = (await network.dumpPeers()).filter(
159
176
  (nodePeer) =>
@@ -1,6 +1,6 @@
1
1
  import bearerAuthPlugin from "@fastify/bearer-auth";
2
2
  import {fastifyCors} from "@fastify/cors";
3
- import {FastifyInstance, FastifyRequest, errorCodes, fastify} from "fastify";
3
+ import {FastifyError, FastifyInstance, FastifyRequest, errorCodes, fastify} from "fastify";
4
4
  import {parse as parseQueryString} from "qs";
5
5
  import {addSszContentTypeParser} from "@lodestar/api/server";
6
6
  import {ErrorAborted, Gauge, Histogram, Logger} from "@lodestar/utils";
@@ -73,15 +73,17 @@ export class RestApiServer {
73
73
  const server = fastify({
74
74
  logger: false,
75
75
  ajv: {customOptions: {coerceTypes: "array"}},
76
- querystringParser: (str) =>
77
- parseQueryString(str, {
78
- // Array as comma-separated values must be supported to be OpenAPI spec compliant
79
- comma: true,
80
- // Drop support for array query strings like `id[0]=1&id[1]=2&id[2]=3` as those are not required to
81
- // be OpenAPI spec compliant and results are inconsistent, see https://github.com/ljharb/qs/issues/331.
82
- // The schema validation will catch this and throw an error as parsed query string results in an object.
83
- parseArrays: false,
84
- }),
76
+ routerOptions: {
77
+ querystringParser: (str) =>
78
+ parseQueryString(str, {
79
+ // Array as comma-separated values must be supported to be OpenAPI spec compliant
80
+ comma: true,
81
+ // Drop support for array query strings like `id[0]=1&id[1]=2&id[2]=3` as those are not required to
82
+ // be OpenAPI spec compliant and results are inconsistent, see https://github.com/ljharb/qs/issues/331.
83
+ // The schema validation will catch this and throw an error as parsed query string results in an object.
84
+ parseArrays: false,
85
+ }),
86
+ },
85
87
  bodyLimit: opts.bodyLimit,
86
88
  http: {maxHeaderSize: opts.headerLimit},
87
89
  });
@@ -91,10 +93,10 @@ export class RestApiServer {
91
93
  this.activeSockets = new HttpActiveSocketsTracker(server.server, metrics);
92
94
 
93
95
  // To parse our ApiError -> statusCode
94
- server.setErrorHandler((err, _req, res) => {
96
+ server.setErrorHandler<FastifyError | Error>((err, _req, res) => {
95
97
  const stacktraces = opts.stacktraces ? err.stack?.split("\n") : undefined;
96
- if (err.validation) {
97
- const {instancePath, message} = err.validation[0];
98
+ if ("validation" in err && err.validation) {
99
+ const {instancePath = "unknown", message} = err.validation?.[0] ?? {};
98
100
  const payload: ErrorResponse = {
99
101
  code: 400,
100
102
  message: `${instancePath.substring(instancePath.lastIndexOf("/") + 1)} ${message}`,
@@ -8,6 +8,7 @@ import {
8
8
  createCachedBeaconState,
9
9
  stateTransition,
10
10
  } from "@lodestar/state-transition";
11
+ import {byteArrayEquals} from "@lodestar/utils";
11
12
  import {IBeaconDb} from "../../../db/index.js";
12
13
  import {getStateTypeFromBytes} from "../../../util/multifork.js";
13
14
  import {HistoricalStateRegenMetrics} from "./metrics.js";
@@ -98,7 +99,7 @@ export async function getHistoricalState(
98
99
  throw e;
99
100
  }
100
101
  blockCount++;
101
- if (Buffer.compare(state.hashTreeRoot(), block.message.stateRoot) !== 0) {
102
+ if (!byteArrayEquals(state.hashTreeRoot(), block.message.stateRoot)) {
102
103
  metrics?.regenErrorCount.inc({reason: RegenErrorType.invalidStateRoot});
103
104
  }
104
105
  }
@@ -1,6 +1,6 @@
1
1
  import {ForkName, ForkPostFulu, ForkPreDeneb, ForkPreGloas, NUMBER_OF_COLUMNS} from "@lodestar/params";
2
2
  import {BeaconBlockBody, BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
3
- import {fromHex, prettyBytes, toRootHex, withTimeout} from "@lodestar/utils";
3
+ import {byteArrayEquals, fromHex, prettyBytes, toRootHex, withTimeout} from "@lodestar/utils";
4
4
  import {VersionedHashes} from "../../../execution/index.js";
5
5
  import {kzgCommitmentToVersionedHash} from "../../../util/blobs.js";
6
6
  import {BlockInputError, BlockInputErrorCode} from "./errors.js";
@@ -529,7 +529,7 @@ function blockAndBlobArePaired(block: SignedBeaconBlock<ForkBlobsDA>, blobSideca
529
529
  if (!blockCommitment || !blobSidecar.kzgCommitment) {
530
530
  return false;
531
531
  }
532
- return Buffer.compare(blockCommitment, blobSidecar.kzgCommitment) === 0;
532
+ return byteArrayEquals(blockCommitment, blobSidecar.kzgCommitment);
533
533
  }
534
534
 
535
535
  function assertBlockAndBlobArePaired(
@@ -5,9 +5,8 @@ import {
5
5
  StateHashTreeRootSource,
6
6
  stateTransition,
7
7
  } from "@lodestar/state-transition";
8
- import {ErrorAborted, Logger} from "@lodestar/utils";
8
+ import {ErrorAborted, Logger, byteArrayEquals} from "@lodestar/utils";
9
9
  import {Metrics} from "../../metrics/index.js";
10
- import {byteArrayEquals} from "../../util/bytes.js";
11
10
  import {nextEventLoop} from "../../util/eventLoop.js";
12
11
  import {BlockError, BlockErrorCode} from "../errors/index.js";
13
12
  import {BlockProcessOpts} from "../options.js";
@@ -2,7 +2,7 @@ import {ChainForkConfig} from "@lodestar/config";
2
2
  import {ZERO_HASH} from "@lodestar/params";
3
3
  import {BeaconStateAllForks, computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
4
4
  import {SignedBeaconBlock, ssz} from "@lodestar/types";
5
- import {Logger, toHex, toRootHex} from "@lodestar/utils";
5
+ import {Logger, byteArrayEquals, toHex, toRootHex} from "@lodestar/utils";
6
6
  import {GENESIS_SLOT} from "../constants/index.js";
7
7
  import {IBeaconDb} from "../db/index.js";
8
8
  import {Metrics} from "../metrics/index.js";
@@ -26,7 +26,7 @@ export async function persistAnchorState(
26
26
 
27
27
  const latestBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(latestBlockHeader);
28
28
 
29
- if (Buffer.compare(blockRoot, latestBlockRoot) !== 0) {
29
+ if (!byteArrayEquals(blockRoot, latestBlockRoot)) {
30
30
  throw Error(
31
31
  `Genesis block root ${toRootHex(blockRoot)} does not match genesis state latest block root ${toRootHex(latestBlockRoot)}`
32
32
  );
@@ -46,12 +46,11 @@ import {
46
46
  ssz,
47
47
  sszTypesFor,
48
48
  } from "@lodestar/types";
49
- import {Logger, MapDef, pruneSetToMax, toRootHex} from "@lodestar/utils";
49
+ import {Logger, MapDef, byteArrayEquals, pruneSetToMax, toRootHex} from "@lodestar/utils";
50
50
  import {ZERO_HASH} from "../../constants/index.js";
51
51
  import {IBeaconDb} from "../../db/index.js";
52
52
  import {NUM_WITNESS, NUM_WITNESS_ELECTRA} from "../../db/repositories/lightclientSyncCommitteeWitness.js";
53
53
  import {Metrics} from "../../metrics/index.js";
54
- import {byteArrayEquals} from "../../util/bytes.js";
55
54
  import {IClock} from "../../util/clock.js";
56
55
  import {ChainEventEmitter} from "../emitter.js";
57
56
  import {LightClientServerError, LightClientServerErrorCode} from "../errors/lightClientError.js";
@@ -3,7 +3,7 @@ import {CheckpointWithHex} from "@lodestar/fork-choice";
3
3
  import {ForkName, ForkPostFulu, ForkPreGloas, isForkPostDeneb, isForkPostFulu, isForkPostGloas} from "@lodestar/params";
4
4
  import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
5
5
  import {BLSSignature, RootHex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
6
- import {LodestarError, Logger, pruneSetToMax} from "@lodestar/utils";
6
+ import {LodestarError, Logger, byteArrayEquals, pruneSetToMax} from "@lodestar/utils";
7
7
  import {Metrics} from "../../metrics/metrics.js";
8
8
  import {IClock} from "../../util/clock.js";
9
9
  import {CustodyConfig} from "../../util/dataColumns.js";
@@ -344,7 +344,7 @@ export class SeenBlockInput {
344
344
  return false;
345
345
  }
346
346
  // Only consider verified if the signature matches
347
- return Buffer.compare(cachedSignature, signature) === 0;
347
+ return byteArrayEquals(cachedSignature, signature);
348
348
  }
349
349
 
350
350
  /**
@@ -20,7 +20,8 @@ export async function serializeState<T>(
20
20
  stateBytes = bufferWithKey.buffer;
21
21
  const dataView = new DataView(stateBytes.buffer, stateBytes.byteOffset, stateBytes.byteLength);
22
22
  state.serializeToBytes({uint8Array: stateBytes, dataView}, 0);
23
- return processFn(stateBytes);
23
+ // Await to ensure buffer is not released back to pool until processFn completes
24
+ return await processFn(stateBytes);
24
25
  }
25
26
  // release the buffer back to the pool automatically
26
27
  }
@@ -12,7 +12,7 @@ import {
12
12
  getBlockHeaderProposerSignatureSetByParentStateSlot,
13
13
  } from "@lodestar/state-transition";
14
14
  import {BlobIndex, Root, Slot, SubnetID, deneb, ssz} from "@lodestar/types";
15
- import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";
15
+ import {byteArrayEquals, toRootHex, verifyMerkleBranch} from "@lodestar/utils";
16
16
  import {kzg} from "../../util/kzg.js";
17
17
  import {BlobSidecarErrorCode, BlobSidecarGossipError, BlobSidecarValidationError} from "../errors/blobSidecarError.js";
18
18
  import {GossipAction} from "../errors/gossipValidation.js";
@@ -226,7 +226,7 @@ export async function validateBlockBlobSidecars(
226
226
  const firstSidecarSignedBlockHeader = blobSidecars[0].signedBlockHeader;
227
227
  const firstSidecarBlockHeader = firstSidecarSignedBlockHeader.message;
228
228
  const firstBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(firstSidecarBlockHeader);
229
- if (Buffer.compare(blockRoot, firstBlockRoot) !== 0) {
229
+ if (!byteArrayEquals(blockRoot, firstBlockRoot)) {
230
230
  throw new BlobSidecarValidationError(
231
231
  {
232
232
  code: BlobSidecarErrorCode.INCORRECT_BLOCK,
@@ -11,7 +11,7 @@ import {
11
11
  getBlockHeaderProposerSignatureSetByParentStateSlot,
12
12
  } from "@lodestar/state-transition";
13
13
  import {Root, Slot, SubnetID, fulu, ssz} from "@lodestar/types";
14
- import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";
14
+ import {byteArrayEquals, toRootHex, verifyMerkleBranch} from "@lodestar/utils";
15
15
  import {Metrics} from "../../metrics/metrics.js";
16
16
  import {kzg} from "../../util/kzg.js";
17
17
  import {
@@ -318,7 +318,7 @@ export async function validateBlockDataColumnSidecars(
318
318
  const firstSidecarSignedBlockHeader = dataColumnSidecars[0].signedBlockHeader;
319
319
  const firstSidecarBlockHeader = firstSidecarSignedBlockHeader.message;
320
320
  const firstBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(firstSidecarBlockHeader);
321
- if (Buffer.compare(blockRoot, firstBlockRoot) !== 0) {
321
+ if (!byteArrayEquals(blockRoot, firstBlockRoot)) {
322
322
  throw new DataColumnSidecarValidationError(
323
323
  {
324
324
  code: DataColumnSidecarErrorCode.INCORRECT_BLOCK,
@@ -454,6 +454,18 @@ export class NetworkCore implements INetworkCore {
454
454
  await this.libp2p.hangUp(peerIdFromString(peerIdStr));
455
455
  }
456
456
 
457
+ async addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
458
+ return this.gossip.addDirectPeer(peer);
459
+ }
460
+
461
+ async removeDirectPeer(peerIdStr: PeerIdStr): Promise<boolean> {
462
+ return this.gossip.removeDirectPeer(peerIdStr);
463
+ }
464
+
465
+ async getDirectPeers(): Promise<string[]> {
466
+ return this.gossip.getDirectPeers();
467
+ }
468
+
457
469
  private _dumpPeer(peerIdStr: string, connections: Connection[]): routes.lodestar.LodestarNodePeer {
458
470
  const peerData = this.peersData.connectedPeers.get(peerIdStr);
459
471
  const fork = this.config.getForkName(this.clock.currentSlot);
@@ -153,6 +153,9 @@ const libp2pWorkerApi: NetworkWorkerApi = {
153
153
  getConnectedPeerCount: () => core.getConnectedPeerCount(),
154
154
  connectToPeer: (peer, multiaddr) => core.connectToPeer(peer, multiaddr),
155
155
  disconnectPeer: (peer) => core.disconnectPeer(peer),
156
+ addDirectPeer: (peer) => core.addDirectPeer(peer),
157
+ removeDirectPeer: (peerId) => core.removeDirectPeer(peerId),
158
+ getDirectPeers: () => core.getDirectPeers(),
156
159
  dumpPeers: () => core.dumpPeers(),
157
160
  dumpPeer: (peerIdStr) => core.dumpPeer(peerIdStr),
158
161
  dumpPeerScoreStats: () => core.dumpPeerScoreStats(),
@@ -247,6 +247,15 @@ export class WorkerNetworkCore implements INetworkCore {
247
247
  disconnectPeer(peer: PeerIdStr): Promise<void> {
248
248
  return this.getApi().disconnectPeer(peer);
249
249
  }
250
+ addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
251
+ return this.getApi().addDirectPeer(peer);
252
+ }
253
+ removeDirectPeer(peerId: PeerIdStr): Promise<boolean> {
254
+ return this.getApi().removeDirectPeer(peerId);
255
+ }
256
+ getDirectPeers(): Promise<string[]> {
257
+ return this.getApi().getDirectPeers();
258
+ }
250
259
  dumpPeers(): Promise<routes.lodestar.LodestarNodePeer[]> {
251
260
  return this.getApi().dumpPeers();
252
261
  }
@@ -30,6 +30,12 @@ export interface INetworkCorePublic {
30
30
  // Debug
31
31
  connectToPeer(peer: PeerIdStr, multiaddr: MultiaddrStr[]): Promise<void>;
32
32
  disconnectPeer(peer: PeerIdStr): Promise<void>;
33
+
34
+ // Direct peers management
35
+ addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null>;
36
+ removeDirectPeer(peerId: PeerIdStr): Promise<boolean>;
37
+ getDirectPeers(): Promise<string[]>;
38
+
33
39
  dumpPeers(): Promise<routes.lodestar.LodestarNodePeer[]>;
34
40
  dumpPeer(peerIdStr: PeerIdStr): Promise<routes.lodestar.LodestarNodePeer | undefined>;
35
41
  dumpPeerScoreStats(): Promise<PeerScoreStats>;
@@ -5,6 +5,7 @@ import {GossipSub, GossipsubEvents} from "@chainsafe/libp2p-gossipsub";
5
5
  import {MetricsRegister, TopicLabel, TopicStrToLabel} from "@chainsafe/libp2p-gossipsub/metrics";
6
6
  import {PeerScoreParams} from "@chainsafe/libp2p-gossipsub/score";
7
7
  import {AddrInfo, SignaturePolicy, TopicStr} from "@chainsafe/libp2p-gossipsub/types";
8
+ import {routes} from "@lodestar/api";
8
9
  import {BeaconConfig, ForkBoundary} from "@lodestar/config";
9
10
  import {ATTESTATION_SUBNET_COUNT, SLOTS_PER_EPOCH, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params";
10
11
  import {SubnetID} from "@lodestar/types";
@@ -87,6 +88,7 @@ export class Eth2Gossipsub extends GossipSub {
87
88
  private readonly logger: Logger;
88
89
  private readonly peersData: PeersData;
89
90
  private readonly events: NetworkEventBus;
91
+ private readonly libp2p: Libp2p;
90
92
 
91
93
  // Internal caches
92
94
  private readonly gossipTopicCache: GossipTopicCache;
@@ -159,6 +161,7 @@ export class Eth2Gossipsub extends GossipSub {
159
161
  this.logger = logger;
160
162
  this.peersData = peersData;
161
163
  this.events = events;
164
+ this.libp2p = modules.libp2p;
162
165
  this.gossipTopicCache = gossipTopicCache;
163
166
 
164
167
  this.addEventListener("gossipsub:message", this.onGossipsubMessage.bind(this));
@@ -341,6 +344,64 @@ export class Eth2Gossipsub extends GossipSub {
341
344
  this.reportMessageValidationResult(data.msgId, data.propagationSource, data.acceptance);
342
345
  });
343
346
  }
347
+
348
+ /**
349
+ * Add a peer as a direct peer at runtime. Accepts multiaddr with peer ID or ENR string.
350
+ * Direct peers maintain permanent mesh connections without GRAFT/PRUNE negotiation.
351
+ */
352
+ async addDirectPeer(peerStr: routes.lodestar.DirectPeer): Promise<string | null> {
353
+ const parsed = parseDirectPeers([peerStr], this.logger);
354
+ if (parsed.length === 0) {
355
+ return null;
356
+ }
357
+
358
+ const {id: peerId, addrs} = parsed[0];
359
+ const peerIdStr = peerId.toString();
360
+
361
+ // Prevent adding self as a direct peer
362
+ if (peerId.equals(this.libp2p.peerId)) {
363
+ this.logger.warn("Cannot add self as a direct peer", {peerId: peerIdStr});
364
+ return null;
365
+ }
366
+
367
+ // Direct peers need addresses to connect - reject if none provided
368
+ if (addrs.length === 0) {
369
+ this.logger.warn("Cannot add direct peer without addresses", {peerId: peerIdStr});
370
+ return null;
371
+ }
372
+
373
+ // Add addresses to peer store first so we can connect
374
+ try {
375
+ await this.libp2p.peerStore.merge(peerId, {multiaddrs: addrs});
376
+ } catch (e) {
377
+ this.logger.warn("Failed to add direct peer addresses to peer store", {peerId: peerIdStr}, e as Error);
378
+ return null;
379
+ }
380
+
381
+ // Add to direct peers set only after addresses are stored
382
+ this.direct.add(peerIdStr);
383
+
384
+ this.logger.info("Added direct peer via API", {peerId: peerIdStr});
385
+ return peerIdStr;
386
+ }
387
+
388
+ /**
389
+ * Remove a peer from direct peers.
390
+ */
391
+ removeDirectPeer(peerIdStr: string): boolean {
392
+ const removed = this.direct.delete(peerIdStr);
393
+ if (removed) {
394
+ this.logger.info("Removed direct peer via API", {peerId: peerIdStr});
395
+ }
396
+ return removed;
397
+ }
398
+
399
+ /**
400
+ * Get list of current direct peer IDs.
401
+ */
402
+ getDirectPeers(): string[] {
403
+ return Array.from(this.direct);
404
+ }
344
405
  }
345
406
 
346
407
  /**
@@ -406,7 +467,7 @@ function getForkBoundaryLabel(boundary: ForkBoundary): ForkBoundaryLabel {
406
467
  * For multiaddrs, the string must contain a /p2p/ component with the peer ID.
407
468
  * For ENRs, the TCP multiaddr and peer ID are extracted from the encoded record.
408
469
  */
409
- export function parseDirectPeers(directPeerStrs: string[], logger: Logger): AddrInfo[] {
470
+ export function parseDirectPeers(directPeerStrs: routes.lodestar.DirectPeer[], logger: Logger): AddrInfo[] {
410
471
  const directPeers: AddrInfo[] = [];
411
472
 
412
473
  for (const peerStr of directPeerStrs) {
@@ -641,6 +641,18 @@ export class Network implements INetwork {
641
641
  return this.core.disconnectPeer(peer);
642
642
  }
643
643
 
644
+ addDirectPeer(peer: routes.lodestar.DirectPeer): Promise<string | null> {
645
+ return this.core.addDirectPeer(peer);
646
+ }
647
+
648
+ removeDirectPeer(peerId: string): Promise<boolean> {
649
+ return this.core.removeDirectPeer(peerId);
650
+ }
651
+
652
+ getDirectPeers(): Promise<string[]> {
653
+ return this.core.getDirectPeers();
654
+ }
655
+
644
656
  dumpPeer(peerIdStr: string): Promise<routes.lodestar.LodestarNodePeer | undefined> {
645
657
  return this.core.dumpPeer(peerIdStr);
646
658
  }
@@ -4,13 +4,12 @@ import {BeaconConfig, ChainForkConfig} from "@lodestar/config";
4
4
  import {SLOTS_PER_EPOCH} from "@lodestar/params";
5
5
  import {BeaconStateAllForks, blockToHeader, computeAnchorCheckpoint} from "@lodestar/state-transition";
6
6
  import {Root, SignedBeaconBlock, Slot, phase0, ssz} from "@lodestar/types";
7
- import {ErrorAborted, Logger, sleep, toRootHex} from "@lodestar/utils";
7
+ import {ErrorAborted, Logger, byteArrayEquals, sleep, toRootHex} from "@lodestar/utils";
8
8
  import {IBeaconChain} from "../../chain/index.js";
9
9
  import {GENESIS_SLOT, ZERO_HASH} from "../../constants/index.js";
10
10
  import {IBeaconDb} from "../../db/index.js";
11
11
  import {Metrics} from "../../metrics/metrics.js";
12
12
  import {INetwork, NetworkEvent, NetworkEventData, PeerAction} from "../../network/index.js";
13
- import {byteArrayEquals} from "../../util/bytes.js";
14
13
  import {ItTrigger} from "../../util/itTrigger.js";
15
14
  import {PeerIdStr} from "../../util/peerId.js";
16
15
  import {shuffleOne} from "../../util/shuffle.js";
@@ -8,7 +8,7 @@ import {
8
8
  isForkPostGloas,
9
9
  } from "@lodestar/params";
10
10
  import {SignedBeaconBlock, Slot, deneb, fulu, phase0} from "@lodestar/types";
11
- import {LodestarError, Logger, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
11
+ import {LodestarError, Logger, byteArrayEquals, fromHex, prettyPrintIndices, toRootHex} from "@lodestar/utils";
12
12
  import {
13
13
  BlockInputSource,
14
14
  DAType,
@@ -475,7 +475,7 @@ export function validateBlockByRangeResponse(
475
475
  if (i < blocks.length - 1) {
476
476
  // compare the block root against the next block's parent root
477
477
  const parentRoot = blocks[i + 1].message.parentRoot;
478
- if (Buffer.compare(blockRoot, parentRoot) !== 0) {
478
+ if (!byteArrayEquals(blockRoot, parentRoot)) {
479
479
  throw new DownloadByRangeError(
480
480
  {
481
481
  code: DownloadByRangeErrorCode.PARENT_ROOT_MISMATCH,
@@ -9,7 +9,7 @@ import {
9
9
  isForkPostFulu,
10
10
  } from "@lodestar/params";
11
11
  import {BeaconBlockBody, BlobIndex, ColumnIndex, SignedBeaconBlock, Slot, deneb, fulu} from "@lodestar/types";
12
- import {LodestarError, fromHex, prettyPrintIndices, toHex, toRootHex} from "@lodestar/utils";
12
+ import {LodestarError, byteArrayEquals, fromHex, prettyPrintIndices, toHex, toRootHex} from "@lodestar/utils";
13
13
  import {isBlockInputBlobs, isBlockInputColumns} from "../../chain/blocks/blockInput/blockInput.js";
14
14
  import {BlockInputSource, IBlockInput} from "../../chain/blocks/blockInput/types.js";
15
15
  import {ChainEventEmitter} from "../../chain/emitter.js";
@@ -19,7 +19,6 @@ import {validateBlockDataColumnSidecars} from "../../chain/validation/dataColumn
19
19
  import {INetwork} from "../../network/interface.js";
20
20
  import {PeerSyncMeta} from "../../network/peers/peersData.js";
21
21
  import {prettyPrintPeerIdStr} from "../../network/util.js";
22
- import {byteArrayEquals} from "../../util/bytes.js";
23
22
  import {PeerIdStr} from "../../util/peerId.js";
24
23
  import {WarnResult} from "../../util/wrapError.js";
25
24
  import {
@@ -1,3 +0,0 @@
1
- import { Root } from "@lodestar/types";
2
- export declare function byteArrayEquals(a: Uint8Array | Root, b: Uint8Array | Root): boolean;
3
- //# sourceMappingURL=bytes.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../../src/util/bytes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,IAAI,EAAC,MAAM,iBAAiB,CAAC;AAErC,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,IAAI,EAAE,CAAC,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAQnF"}
package/lib/util/bytes.js DELETED
@@ -1,11 +0,0 @@
1
- export function byteArrayEquals(a, b) {
2
- if (a.length !== b.length) {
3
- return false;
4
- }
5
- for (let i = 0; i < a.length; i++) {
6
- if (a[i] !== b[i])
7
- return false;
8
- }
9
- return true;
10
- }
11
- //# sourceMappingURL=bytes.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"bytes.js","sourceRoot":"","sources":["../../src/util/bytes.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,eAAe,CAAC,CAAoB,EAAE,CAAoB;IACxE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
package/src/util/bytes.ts DELETED
@@ -1,11 +0,0 @@
1
- import {Root} from "@lodestar/types";
2
-
3
- export function byteArrayEquals(a: Uint8Array | Root, b: Uint8Array | Root): boolean {
4
- if (a.length !== b.length) {
5
- return false;
6
- }
7
- for (let i = 0; i < a.length; i++) {
8
- if (a[i] !== b[i]) return false;
9
- }
10
- return true;
11
- }