@lodestar/beacon-node 1.47.0-dev.16cb8ec36c → 1.47.0-dev.1c8babbe6e

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 (175) hide show
  1. package/lib/api/impl/beacon/blocks/index.js +1 -1
  2. package/lib/api/impl/beacon/blocks/index.js.map +1 -1
  3. package/lib/api/impl/beacon/pool/index.d.ts.map +1 -1
  4. package/lib/api/impl/beacon/pool/index.js +8 -1
  5. package/lib/api/impl/beacon/pool/index.js.map +1 -1
  6. package/lib/api/impl/beacon/state/utils.d.ts +2 -1
  7. package/lib/api/impl/beacon/state/utils.d.ts.map +1 -1
  8. package/lib/api/impl/beacon/state/utils.js.map +1 -1
  9. package/lib/api/impl/events/index.d.ts.map +1 -1
  10. package/lib/api/impl/events/index.js +2 -0
  11. package/lib/api/impl/events/index.js.map +1 -1
  12. package/lib/api/rest/base.d.ts.map +1 -1
  13. package/lib/api/rest/base.js +7 -0
  14. package/lib/api/rest/base.js.map +1 -1
  15. package/lib/chain/blocks/importExecutionPayload.d.ts +3 -2
  16. package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
  17. package/lib/chain/blocks/importExecutionPayload.js +11 -9
  18. package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
  19. package/lib/chain/blocks/index.d.ts.map +1 -1
  20. package/lib/chain/blocks/index.js +14 -6
  21. package/lib/chain/blocks/index.js.map +1 -1
  22. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
  23. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +22 -12
  24. package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
  25. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts +2 -2
  26. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.d.ts.map +1 -1
  27. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js +2 -2
  28. package/lib/chain/blocks/verifyExecutionPayloadEnvelope.js.map +1 -1
  29. package/lib/chain/bls/index.d.ts +1 -1
  30. package/lib/chain/bls/index.d.ts.map +1 -1
  31. package/lib/chain/bls/interface.d.ts +5 -5
  32. package/lib/chain/bls/interface.d.ts.map +1 -1
  33. package/lib/chain/bls/multithread/index.d.ts +5 -11
  34. package/lib/chain/bls/multithread/index.d.ts.map +1 -1
  35. package/lib/chain/bls/multithread/index.js +116 -101
  36. package/lib/chain/bls/multithread/index.js.map +1 -1
  37. package/lib/chain/bls/multithread/jobItem.d.ts +5 -19
  38. package/lib/chain/bls/multithread/jobItem.d.ts.map +1 -1
  39. package/lib/chain/bls/multithread/jobItem.js +10 -65
  40. package/lib/chain/bls/multithread/jobItem.js.map +1 -1
  41. package/lib/chain/bls/multithread/types.d.ts +29 -8
  42. package/lib/chain/bls/multithread/types.d.ts.map +1 -1
  43. package/lib/chain/bls/multithread/types.js +12 -0
  44. package/lib/chain/bls/multithread/types.js.map +1 -1
  45. package/lib/chain/bls/multithread/utils.d.ts +4 -0
  46. package/lib/chain/bls/multithread/utils.d.ts.map +1 -1
  47. package/lib/chain/bls/multithread/utils.js +10 -0
  48. package/lib/chain/bls/multithread/utils.js.map +1 -1
  49. package/lib/chain/bls/multithread/worker.js +49 -23
  50. package/lib/chain/bls/multithread/worker.js.map +1 -1
  51. package/lib/chain/bls/singleThread.d.ts +4 -10
  52. package/lib/chain/bls/singleThread.d.ts.map +1 -1
  53. package/lib/chain/bls/singleThread.js +8 -45
  54. package/lib/chain/bls/singleThread.js.map +1 -1
  55. package/lib/chain/bls/utils.d.ts +4 -4
  56. package/lib/chain/bls/utils.d.ts.map +1 -1
  57. package/lib/chain/bls/utils.js +18 -19
  58. package/lib/chain/bls/utils.js.map +1 -1
  59. package/lib/chain/chain.d.ts +2 -1
  60. package/lib/chain/chain.d.ts.map +1 -1
  61. package/lib/chain/chain.js +5 -3
  62. package/lib/chain/chain.js.map +1 -1
  63. package/lib/chain/errors/blockError.d.ts +1 -0
  64. package/lib/chain/errors/blockError.d.ts.map +1 -1
  65. package/lib/chain/errors/blockError.js +6 -1
  66. package/lib/chain/errors/blockError.js.map +1 -1
  67. package/lib/chain/interface.d.ts +2 -1
  68. package/lib/chain/interface.d.ts.map +1 -1
  69. package/lib/chain/interface.js.map +1 -1
  70. package/lib/chain/opPools/aggregatedAttestationPool.js +1 -1
  71. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  72. package/lib/chain/opPools/attestationPool.d.ts +1 -1
  73. package/lib/chain/opPools/attestationPool.d.ts.map +1 -1
  74. package/lib/chain/opPools/attestationPool.js +1 -1
  75. package/lib/chain/opPools/attestationPool.js.map +1 -1
  76. package/lib/chain/opPools/payloadAttestationPool.js +1 -1
  77. package/lib/chain/opPools/payloadAttestationPool.js.map +1 -1
  78. package/lib/chain/opPools/syncCommitteeMessagePool.js +1 -1
  79. package/lib/chain/opPools/syncCommitteeMessagePool.js.map +1 -1
  80. package/lib/chain/opPools/syncContributionAndProofPool.js +1 -1
  81. package/lib/chain/opPools/syncContributionAndProofPool.js.map +1 -1
  82. package/lib/chain/opPools/utils.d.ts +1 -1
  83. package/lib/chain/opPools/utils.d.ts.map +1 -1
  84. package/lib/chain/opPools/utils.js +1 -1
  85. package/lib/chain/opPools/utils.js.map +1 -1
  86. package/lib/chain/validation/attestation.d.ts.map +1 -1
  87. package/lib/chain/validation/attestation.js +1 -4
  88. package/lib/chain/validation/attestation.js.map +1 -1
  89. package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
  90. package/lib/chain/validation/attesterSlashing.js +1 -1
  91. package/lib/chain/validation/attesterSlashing.js.map +1 -1
  92. package/lib/chain/validation/executionPayloadBid.d.ts.map +1 -1
  93. package/lib/chain/validation/executionPayloadBid.js +1 -2
  94. package/lib/chain/validation/executionPayloadBid.js.map +1 -1
  95. package/lib/chain/validation/executionPayloadEnvelope.js +1 -1
  96. package/lib/chain/validation/executionPayloadEnvelope.js.map +1 -1
  97. package/lib/chain/validation/payloadAttestationMessage.js +2 -9
  98. package/lib/chain/validation/payloadAttestationMessage.js.map +1 -1
  99. package/lib/chain/validation/proposerPreferences.js +2 -2
  100. package/lib/chain/validation/proposerPreferences.js.map +1 -1
  101. package/lib/chain/validation/proposerSlashing.js +1 -1
  102. package/lib/chain/validation/proposerSlashing.js.map +1 -1
  103. package/lib/metrics/metrics/lodestar.d.ts +38 -18
  104. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  105. package/lib/metrics/metrics/lodestar.js +80 -50
  106. package/lib/metrics/metrics/lodestar.js.map +1 -1
  107. package/lib/network/gossip/topic.d.ts +13 -0
  108. package/lib/network/gossip/topic.d.ts.map +1 -1
  109. package/lib/network/gossip/topic.js +37 -0
  110. package/lib/network/gossip/topic.js.map +1 -1
  111. package/lib/network/network.d.ts.map +1 -1
  112. package/lib/network/network.js +22 -43
  113. package/lib/network/network.js.map +1 -1
  114. package/lib/network/util.d.ts +0 -1
  115. package/lib/network/util.d.ts.map +1 -1
  116. package/lib/network/util.js +0 -4
  117. package/lib/network/util.js.map +1 -1
  118. package/lib/node/nodejs.d.ts +2 -1
  119. package/lib/node/nodejs.d.ts.map +1 -1
  120. package/lib/node/nodejs.js.map +1 -1
  121. package/lib/sync/range/batch.d.ts +23 -5
  122. package/lib/sync/range/batch.d.ts.map +1 -1
  123. package/lib/sync/range/batch.js +61 -22
  124. package/lib/sync/range/batch.js.map +1 -1
  125. package/lib/sync/range/chain.d.ts +2 -1
  126. package/lib/sync/range/chain.d.ts.map +1 -1
  127. package/lib/sync/range/chain.js +23 -10
  128. package/lib/sync/range/chain.js.map +1 -1
  129. package/package.json +18 -18
  130. package/src/api/impl/beacon/blocks/index.ts +1 -1
  131. package/src/api/impl/beacon/pool/index.ts +7 -1
  132. package/src/api/impl/beacon/state/utils.ts +2 -1
  133. package/src/api/impl/events/index.ts +3 -1
  134. package/src/api/rest/base.ts +7 -0
  135. package/src/chain/blocks/importExecutionPayload.ts +12 -11
  136. package/src/chain/blocks/index.ts +18 -6
  137. package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +25 -16
  138. package/src/chain/blocks/verifyExecutionPayloadEnvelope.ts +1 -9
  139. package/src/chain/bls/index.ts +1 -1
  140. package/src/chain/bls/interface.ts +7 -3
  141. package/src/chain/bls/multithread/index.ts +148 -120
  142. package/src/chain/bls/multithread/jobItem.ts +12 -82
  143. package/src/chain/bls/multithread/types.ts +33 -8
  144. package/src/chain/bls/multithread/utils.ts +11 -0
  145. package/src/chain/bls/multithread/worker.ts +85 -27
  146. package/src/chain/bls/singleThread.ts +11 -55
  147. package/src/chain/bls/utils.ts +23 -24
  148. package/src/chain/chain.ts +6 -4
  149. package/src/chain/errors/blockError.ts +8 -2
  150. package/src/chain/interface.ts +2 -1
  151. package/src/chain/opPools/aggregatedAttestationPool.ts +1 -1
  152. package/src/chain/opPools/attestationPool.ts +1 -1
  153. package/src/chain/opPools/payloadAttestationPool.ts +1 -1
  154. package/src/chain/opPools/syncCommitteeMessagePool.ts +1 -1
  155. package/src/chain/opPools/syncContributionAndProofPool.ts +1 -1
  156. package/src/chain/opPools/utils.ts +1 -1
  157. package/src/chain/validation/attestation.ts +1 -7
  158. package/src/chain/validation/attesterSlashing.ts +1 -8
  159. package/src/chain/validation/executionPayloadBid.ts +1 -2
  160. package/src/chain/validation/executionPayloadEnvelope.ts +0 -1
  161. package/src/chain/validation/payloadAttestationMessage.ts +3 -11
  162. package/src/chain/validation/proposerPreferences.ts +3 -3
  163. package/src/chain/validation/proposerSlashing.ts +1 -1
  164. package/src/metrics/metrics/lodestar.ts +85 -51
  165. package/src/network/gossip/topic.ts +38 -0
  166. package/src/network/network.ts +27 -55
  167. package/src/network/util.ts +0 -5
  168. package/src/node/nodejs.ts +2 -1
  169. package/src/sync/range/batch.ts +83 -28
  170. package/src/sync/range/chain.ts +26 -11
  171. package/lib/chain/bls/maybeBatch.d.ts +0 -12
  172. package/lib/chain/bls/maybeBatch.d.ts.map +0 -1
  173. package/lib/chain/bls/maybeBatch.js +0 -35
  174. package/lib/chain/bls/maybeBatch.js.map +0 -1
  175. package/src/chain/bls/maybeBatch.ts +0 -45
@@ -31,6 +31,10 @@ import {RegistryMetricCreator} from "../utils/registryMetricCreator.js";
31
31
 
32
32
  export type LodestarMetrics = ReturnType<typeof createLodestarMetrics>;
33
33
 
34
+ type BlsJobOutcome = "valid" | "invalid" | "prepError" | "verifyError" | "workerError";
35
+ type BlsBufferFlushReason = "size" | "timeout";
36
+ type BlsVerificationCallOperation = "batch" | "single" | "fallback" | "same_message";
37
+
34
38
  /**
35
39
  * Extra Lodestar custom metrics
36
40
  */
@@ -396,86 +400,108 @@ export function createLodestarMetrics(
396
400
  // BLS verifier thread pool and queue
397
401
 
398
402
  bls: {
399
- aggregatedPubkeys: register.gauge({
403
+ aggregatedPubkeys: register.counter({
400
404
  name: "lodestar_bls_aggregated_pubkeys_total",
401
- help: "Total aggregated pubkeys for BLS validation",
405
+ help: "Total validator public keys used as inputs to aggregate signature sets",
406
+ }),
407
+ pubkeyCacheSize: register.gauge({
408
+ name: "lodestar_bls_pubkey_cache_size",
409
+ help: "Current number of validator public keys in the native cache",
410
+ }),
411
+ pubkeyCacheCapacity: register.gauge({
412
+ name: "lodestar_bls_pubkey_cache_capacity",
413
+ help: "Number of validator public keys the native cache can hold without growing",
402
414
  }),
403
415
  },
404
416
 
405
417
  blsThreadPool: {
406
- jobsWorkerTime: register.gauge<{workerId: number}>({
418
+ jobsWorkerTime: register.counter<{workerId: number}>({
407
419
  name: "lodestar_bls_thread_pool_time_seconds_sum",
408
- help: "Total time spent verifying signature sets measured on the worker",
420
+ help: "Cumulative wall time spent processing BLS dispatch groups by worker",
409
421
  labelNames: ["workerId"],
410
422
  }),
411
- successJobsSignatureSetsCount: register.gauge({
423
+ successJobsSignatureSetsCount: register.counter({
412
424
  name: "lodestar_bls_thread_pool_success_jobs_signature_sets_count",
413
- help: "Count of total verified signature sets",
414
- }),
415
- errorAggregateSignatureSetsCount: register.gauge<{type: JobQueueItemType}>({
416
- name: "lodestar_bls_thread_pool_error_aggregate_signature_sets_count",
417
- help: "Count of error when aggregating pubkeys or signatures",
418
- labelNames: ["type"],
425
+ help: "Count of signature sets completed without an operational error",
419
426
  }),
420
- errorJobsSignatureSetsCount: register.gauge({
427
+ errorJobsSignatureSetsCount: register.counter({
421
428
  name: "lodestar_bls_thread_pool_error_jobs_signature_sets_count",
422
- help: "Count of total error-ed signature sets",
429
+ help: "Count of signature sets that ended in an input, binding, or worker error",
423
430
  }),
424
- jobWaitTime: register.histogram({
431
+ jobWaitTime: register.histogram<{type: JobQueueItemType}>({
425
432
  name: "lodestar_bls_thread_pool_queue_job_wait_time_seconds",
426
- help: "Time from job added to the queue to starting the job in seconds",
427
- buckets: [0.01, 0.02, 0.1, 0.3, 0.5, 1],
433
+ help: "Time from adding a BLS job until it is selected for a worker dispatch group",
434
+ labelNames: ["type"],
435
+ buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.5, 1, 2],
436
+ }),
437
+ jobDuration: register.histogram<{type: JobQueueItemType}>({
438
+ name: "lodestar_bls_thread_pool_job_duration_seconds",
439
+ help: "End-to-end duration of a BLS job from submission through completion",
440
+ labelNames: ["type"],
441
+ buckets: [0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.5, 1, 2],
442
+ }),
443
+ jobResults: register.counter<{type: JobQueueItemType; outcome: BlsJobOutcome}>({
444
+ name: "lodestar_bls_thread_pool_job_results_total",
445
+ help: "Count of completed BLS jobs by cryptographic or operational outcome",
446
+ labelNames: ["type", "outcome"],
428
447
  }),
429
448
  queueLength: register.gauge({
430
449
  name: "lodestar_bls_thread_pool_queue_length",
431
- help: "Count of total block processor queue length",
450
+ help: "Count of ready BLS jobs waiting for a worker",
451
+ }),
452
+ bufferedQueueLength: register.gauge({
453
+ name: "lodestar_bls_thread_pool_buffered_queue_length",
454
+ help: "Count of BLS jobs waiting in the intentional batching buffer",
432
455
  }),
433
456
  workersBusy: register.gauge({
434
457
  name: "lodestar_bls_thread_pool_workers_busy",
435
458
  help: "Count of current busy workers",
436
459
  }),
437
- totalJobsGroupsStarted: register.gauge({
460
+ totalJobsGroupsStarted: register.counter({
438
461
  name: "lodestar_bls_thread_pool_job_groups_started_total",
439
- help: "Count of total jobs groups started in bls thread pool, job groups include +1 jobs",
462
+ help: "Count of worker dispatch groups started by the BLS thread pool",
440
463
  }),
441
- totalJobsStarted: register.gauge<{type: JobQueueItemType}>({
464
+ totalJobsStarted: register.counter<{type: JobQueueItemType}>({
442
465
  name: "lodestar_bls_thread_pool_jobs_started_total",
443
- help: "Count of total jobs started in bls thread pool, jobs include +1 signature sets",
466
+ help: "Count of jobs started by the BLS thread pool",
444
467
  labelNames: ["type"],
445
468
  }),
446
- totalSigSetsStarted: register.gauge<{type: JobQueueItemType}>({
469
+ totalSigSetsStarted: register.counter<{type: JobQueueItemType}>({
447
470
  name: "lodestar_bls_thread_pool_sig_sets_started_total",
448
- help: "Count of total signature sets started in bls thread pool, sig sets include 1 pk, msg, sig",
471
+ help: "Count of signature sets started by the BLS thread pool",
449
472
  labelNames: ["type"],
450
473
  }),
451
474
  // Re-verifying a batch means doing double work. This number must be very low or it can be a waste of CPU resources
452
- batchRetries: register.gauge({
475
+ batchRetries: register.counter({
453
476
  name: "lodestar_bls_thread_pool_batch_retries_total",
454
- help: "Count of total batches that failed and had to be verified again.",
477
+ help: "Count of combined batches that failed and required per-job fallback",
455
478
  }),
456
479
  // To count how many sigs are being validated with the optimization of batching them
457
- batchSigsSuccess: register.gauge({
480
+ batchSigsSuccess: register.counter({
458
481
  name: "lodestar_bls_thread_pool_batch_sigs_success_total",
459
- help: "Count of total batches that failed and had to be verified again.",
482
+ help: "Count of signature sets accepted by combined batch verification without fallback",
460
483
  }),
461
- sameMessageRetryJobs: register.gauge({
462
- name: "lodestar_bls_thread_pool_same_message_jobs_retries_total",
463
- help: "Count of total same message jobs that failed and had to be verified again.",
484
+ bufferFlushes: register.counter<{reason: BlsBufferFlushReason}>({
485
+ name: "lodestar_bls_thread_pool_buffer_flushes_total",
486
+ help: "Count of batching-buffer flushes by trigger",
487
+ labelNames: ["reason"],
464
488
  }),
465
- sameMessageRetrySets: register.gauge({
466
- name: "lodestar_bls_thread_pool_same_message_sets_retries_total",
467
- help: "Count of total same message sets that failed and had to be verified again.",
489
+ bufferedSignatureSets: register.histogram({
490
+ name: "lodestar_bls_thread_pool_buffered_signature_sets",
491
+ help: "Count of signature sets included in each batching-buffer flush",
492
+ buckets: [1, 8, 16, 24, 28, 32, 36, 40, 44, 48, 64, 128],
468
493
  }),
469
494
  // To measure the time cost of main thread <-> worker message passing
470
495
  latencyToWorker: register.histogram({
471
496
  name: "lodestar_bls_thread_pool_latency_to_worker",
472
497
  help: "Time from sending the job to the worker and the worker receiving it",
473
- buckets: [0.001, 0.003, 0.01, 0.03, 0.1],
498
+ // The dashboard treats 20ms as slow; values above 100ms only need severe-stall classification.
499
+ buckets: [0.00025, 0.001, 0.005, 0.02, 0.1],
474
500
  }),
475
501
  latencyFromWorker: register.histogram({
476
502
  name: "lodestar_bls_thread_pool_latency_from_worker",
477
503
  help: "Time from the worker sending the result and the main thread receiving it",
478
- buckets: [0.001, 0.003, 0.01, 0.03, 0.1],
504
+ buckets: [0.00025, 0.001, 0.005, 0.02, 0.1],
479
505
  }),
480
506
  mainThreadDurationInThreadPool: register.histogram({
481
507
  name: "lodestar_bls_thread_pool_main_thread_time_seconds",
@@ -485,32 +511,40 @@ export function createLodestarMetrics(
485
511
  }),
486
512
  timePerSigSet: register.histogram({
487
513
  name: "lodestar_bls_worker_thread_time_per_sigset_seconds",
488
- help: "Time to verify each sigset with worker thread mode",
514
+ help: "Average BLS worker dispatch wall time per original signature set",
489
515
  // Time per sig ~0.9ms on good machines
490
- buckets: [0.5e-3, 0.75e-3, 1e-3, 1.5e-3, 2e-3, 5e-3],
491
- }),
492
- totalSigSets: register.gauge({
516
+ buckets: [0.5e-3, 0.75e-3, 1e-3, 1.5e-3, 2e-3, 5e-3, 10e-3, 20e-3, 50e-3, 100e-3],
517
+ }),
518
+ workRequestPreparationDuration: register.histogram({
519
+ name: "lodestar_bls_thread_pool_work_request_preparation_duration_seconds",
520
+ help: "Main-thread time spent converting one worker dispatch group to binding inputs",
521
+ // Normal requests take microseconds; the dashboard treats 1ms as a main-thread stall.
522
+ buckets: [0.000005, 0.000025, 0.0001, 0.0005, 0.001, 0.005, 0.025],
523
+ }),
524
+ verificationCallDuration: register.histogram<{operation: BlsVerificationCallOperation}>({
525
+ name: "lodestar_bls_thread_pool_verification_call_duration_seconds",
526
+ help: "Duration of one worker-side lodestar-z verification binding call, including input conversion",
527
+ labelNames: ["operation"],
528
+ // Verification ranges from sub-millisecond calls to large batches; above 500ms is uniformly severe.
529
+ buckets: [0.00025, 0.001, 0.005, 0.02, 0.1, 0.5],
530
+ }),
531
+ verificationCallSignatureSets: register.counter<{operation: BlsVerificationCallOperation}>({
532
+ name: "lodestar_bls_thread_pool_verification_call_signature_sets_total",
533
+ help: "Count of signature sets passed to worker-side lodestar-z verification binding calls",
534
+ labelNames: ["operation"],
535
+ }),
536
+ totalSigSets: register.counter({
493
537
  name: "lodestar_bls_thread_pool_sig_sets_total",
494
538
  help: "Count of total signature sets",
495
539
  }),
496
- prioritizedSigSets: register.gauge({
540
+ prioritizedSigSets: register.counter({
497
541
  name: "lodestar_bls_thread_pool_prioritized_sig_sets_total",
498
542
  help: "Count of total prioritized signature sets",
499
543
  }),
500
- batchableSigSets: register.gauge({
544
+ batchableSigSets: register.counter({
501
545
  name: "lodestar_bls_thread_pool_batchable_sig_sets_total",
502
546
  help: "Count of total batchable signature sets",
503
547
  }),
504
- aggregateWithRandomnessAsyncDuration: register.histogram({
505
- name: "lodestar_bls_thread_pool_aggregate_with_randomness_async_time_seconds",
506
- help: "Total time performing aggregateWithRandomness async",
507
- buckets: [0.001, 0.005, 0.01, 0.1, 0.3],
508
- }),
509
- pubkeysAggregationMainThreadDuration: register.histogram({
510
- name: "lodestar_bls_thread_pool_pubkeys_aggregation_main_thread_time_seconds",
511
- help: "Total time spent aggregating pubkeys on main thread",
512
- buckets: [0.001, 0.005, 0.01, 0.1],
513
- }),
514
548
  },
515
549
 
516
550
  // BLS time on single thread mode
@@ -414,3 +414,41 @@ export const gossipTopicIgnoreDuplicatePublishError: Record<GossipType, boolean>
414
414
  [GossipType.execution_payload_bid]: true,
415
415
  [GossipType.proposer_preferences]: true,
416
416
  };
417
+
418
+ /**
419
+ * Whether a publish that reached zero subscribed peers is an acceptable outcome for a topic.
420
+ *
421
+ * Publishing to a topic with no subscribed peers throws `PublishError.NoPeersSubscribedToTopic`, which
422
+ * propagates to the caller and, for messages submitted through the beacon API, is surfaced to the
423
+ * validator client. That is the right default: a message that reached nobody is a failed broadcast, and a
424
+ * VC configured with several beacon nodes can fall back to another one.
425
+ *
426
+ * Only topics that have a concrete reason to tolerate it opt out here, ie. are set to `true`, and the
427
+ * reason is named on the entry. Everything else is `false` and keeps raising. Note this is applied per
428
+ * publish, a topic set to `false` still honors the global `--network.allowPublishToZeroPeers` flag.
429
+ */
430
+ export const gossipTopicAllowPublishToZeroPeers: Record<GossipType, boolean> = {
431
+ [GossipType.beacon_block]: false,
432
+ [GossipType.blob_sidecar]: false,
433
+ // data_column_sidecar: we ensure having all topic peers via prioritizePeers(). Even with 0 peers on the
434
+ // topic the overall publish can still succeed, because a supernode rebuilds and publishes the missing
435
+ // columns for us, so track sent peers as 0 instead of raising
436
+ [GossipType.data_column_sidecar]: true,
437
+ [GossipType.beacon_aggregate_and_proof]: false,
438
+ [GossipType.beacon_attestation]: false,
439
+ [GossipType.voluntary_exit]: false,
440
+ [GossipType.proposer_slashing]: false,
441
+ [GossipType.attester_slashing]: false,
442
+ [GossipType.sync_committee_contribution_and_proof]: false,
443
+ [GossipType.sync_committee]: false,
444
+ // light_client_finality_update and light_client_optimistic_update: non-mandatory route on most of the
445
+ // network as of Oct 2022, there may be no peer subscribed to the topic at all. Reason carried over from
446
+ // the handlers in network.ts, which used to swallow the error instead
447
+ [GossipType.light_client_finality_update]: true,
448
+ [GossipType.light_client_optimistic_update]: true,
449
+ [GossipType.bls_to_execution_change]: false,
450
+ [GossipType.execution_payload]: false,
451
+ [GossipType.payload_attestation_message]: false,
452
+ [GossipType.execution_payload_bid]: false,
453
+ [GossipType.proposer_preferences]: false,
454
+ };
@@ -49,7 +49,12 @@ import {INetworkCore, NetworkCore, WorkerNetworkCore} from "./core/index.js";
49
49
  import {INetworkEventBus, NetworkEvent, NetworkEventBus, NetworkEventData} from "./events.js";
50
50
  import {getActiveForkBoundaries} from "./forks.js";
51
51
  import {GossipHandlers, GossipTopicMap, GossipType, GossipTypeMap} from "./gossip/index.js";
52
- import {getGossipSSZType, gossipTopicIgnoreDuplicatePublishError, stringifyGossipTopic} from "./gossip/topic.js";
52
+ import {
53
+ getGossipSSZType,
54
+ gossipTopicAllowPublishToZeroPeers,
55
+ gossipTopicIgnoreDuplicatePublishError,
56
+ stringifyGossipTopic,
57
+ } from "./gossip/topic.js";
53
58
  import {INetwork} from "./interface.js";
54
59
  import {NetworkOptions} from "./options.js";
55
60
  import {PeerAction, PeerScoreStats} from "./peers/index.js";
@@ -65,7 +70,7 @@ import {
65
70
  } from "./reqresp/utils/collect.js";
66
71
  import {collectSequentialBlocksInRange} from "./reqresp/utils/collectSequentialBlocksInRange.js";
67
72
  import {CommitteeSubscription} from "./subnets/index.js";
68
- import {isPublishDuplicateError, isPublishToZeroPeersError, prettyPrintPeerIdStr} from "./util.js";
73
+ import {isPublishDuplicateError, prettyPrintPeerIdStr} from "./util.js";
69
74
 
70
75
  type NetworkModules = {
71
76
  opts: NetworkOptions;
@@ -352,9 +357,7 @@ export class Network implements INetwork {
352
357
  const epoch = computeEpochAtSlot(signedBlock.message.slot);
353
358
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
354
359
 
355
- return this.publishGossip<GossipType.beacon_block>({type: GossipType.beacon_block, boundary}, signedBlock, {
356
- ignoreDuplicatePublishError: true,
357
- });
360
+ return this.publishGossip<GossipType.beacon_block>({type: GossipType.beacon_block, boundary}, signedBlock);
358
361
  }
359
362
 
360
363
  async publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise<number> {
@@ -363,9 +366,7 @@ export class Network implements INetwork {
363
366
 
364
367
  const subnet = blobSidecar.index;
365
368
 
366
- return this.publishGossip<GossipType.blob_sidecar>({type: GossipType.blob_sidecar, boundary, subnet}, blobSidecar, {
367
- ignoreDuplicatePublishError: true,
368
- });
369
+ return this.publishGossip<GossipType.blob_sidecar>({type: GossipType.blob_sidecar, boundary, subnet}, blobSidecar);
369
370
  }
370
371
 
371
372
  async publishDataColumnSidecar(
@@ -381,14 +382,7 @@ export class Network implements INetwork {
381
382
  try {
382
383
  const sentPeers = await this.publishGossip<GossipType.data_column_sidecar>(
383
384
  {type: GossipType.data_column_sidecar, boundary, subnet},
384
- dataColumnSidecar,
385
- {
386
- // we ensure having all topic peers via prioritizePeers() function
387
- // in the worse case, if there is 0 peer on the topic, the overall publish operation could be still a success
388
- // because supernode will rebuild and publish missing data column sidecars for us
389
- // hence we want to track sent peers as 0 instead of an error
390
- allowPublishToZeroTopicPeers: true,
391
- }
385
+ dataColumnSidecar
392
386
  );
393
387
  return {sentPeers, alreadyPublished: false};
394
388
  } catch (e) {
@@ -405,8 +399,7 @@ export class Network implements INetwork {
405
399
 
406
400
  return this.publishGossip<GossipType.beacon_aggregate_and_proof>(
407
401
  {type: GossipType.beacon_aggregate_and_proof, boundary},
408
- aggregateAndProof,
409
- {ignoreDuplicatePublishError: true}
402
+ aggregateAndProof
410
403
  );
411
404
  }
412
405
 
@@ -416,8 +409,7 @@ export class Network implements INetwork {
416
409
 
417
410
  return this.publishGossip<GossipType.beacon_attestation>(
418
411
  {type: GossipType.beacon_attestation, boundary, subnet},
419
- attestation,
420
- {ignoreDuplicatePublishError: true}
412
+ attestation
421
413
  );
422
414
  }
423
415
 
@@ -425,9 +417,7 @@ export class Network implements INetwork {
425
417
  const epoch = voluntaryExit.message.epoch;
426
418
  const boundary = this.config.getForkBoundaryAtEpoch(epoch);
427
419
 
428
- return this.publishGossip<GossipType.voluntary_exit>({type: GossipType.voluntary_exit, boundary}, voluntaryExit, {
429
- ignoreDuplicatePublishError: true,
430
- });
420
+ return this.publishGossip<GossipType.voluntary_exit>({type: GossipType.voluntary_exit, boundary}, voluntaryExit);
431
421
  }
432
422
 
433
423
  async publishBlsToExecutionChange(blsToExecutionChange: capella.SignedBLSToExecutionChange): Promise<number> {
@@ -438,8 +428,7 @@ export class Network implements INetwork {
438
428
  if (fork >= ForkSeq.capella) {
439
429
  const publishPromise = this.publishGossip<GossipType.bls_to_execution_change>(
440
430
  {type: GossipType.bls_to_execution_change, boundary},
441
- blsToExecutionChange,
442
- {ignoreDuplicatePublishError: true}
431
+ blsToExecutionChange
443
432
  );
444
433
  publishChanges.push(publishPromise);
445
434
  }
@@ -457,8 +446,7 @@ export class Network implements INetwork {
457
446
 
458
447
  return this.publishGossip<GossipType.proposer_slashing>(
459
448
  {type: GossipType.proposer_slashing, boundary},
460
- proposerSlashing,
461
- {ignoreDuplicatePublishError: true}
449
+ proposerSlashing
462
450
  );
463
451
  }
464
452
 
@@ -478,10 +466,7 @@ export class Network implements INetwork {
478
466
 
479
467
  return this.publishGossip<GossipType.sync_committee>(
480
468
  {type: GossipType.sync_committee, boundary, subnet},
481
- signature,
482
- {
483
- ignoreDuplicatePublishError: true,
484
- }
469
+ signature
485
470
  );
486
471
  }
487
472
 
@@ -491,8 +476,7 @@ export class Network implements INetwork {
491
476
 
492
477
  return this.publishGossip<GossipType.sync_committee_contribution_and_proof>(
493
478
  {type: GossipType.sync_committee_contribution_and_proof, boundary},
494
- contributionAndProof,
495
- {ignoreDuplicatePublishError: true}
479
+ contributionAndProof
496
480
  );
497
481
  }
498
482
 
@@ -522,8 +506,7 @@ export class Network implements INetwork {
522
506
 
523
507
  return this.publishGossip<GossipType.execution_payload>(
524
508
  {type: GossipType.execution_payload, boundary},
525
- signedEnvelope,
526
- {ignoreDuplicatePublishError: true}
509
+ signedEnvelope
527
510
  );
528
511
  }
529
512
 
@@ -533,8 +516,7 @@ export class Network implements INetwork {
533
516
 
534
517
  return this.publishGossip<GossipType.execution_payload_bid>(
535
518
  {type: GossipType.execution_payload_bid, boundary},
536
- signedBid,
537
- {ignoreDuplicatePublishError: true}
519
+ signedBid
538
520
  );
539
521
  }
540
522
 
@@ -544,8 +526,7 @@ export class Network implements INetwork {
544
526
 
545
527
  return this.publishGossip<GossipType.payload_attestation_message>(
546
528
  {type: GossipType.payload_attestation_message, boundary},
547
- payloadAttestationMessage,
548
- {ignoreDuplicatePublishError: true}
529
+ payloadAttestationMessage
549
530
  );
550
531
  }
551
532
 
@@ -555,22 +536,21 @@ export class Network implements INetwork {
555
536
 
556
537
  return this.publishGossip<GossipType.proposer_preferences>(
557
538
  {type: GossipType.proposer_preferences, boundary},
558
- signedProposerPreferences,
559
- {ignoreDuplicatePublishError: true}
539
+ signedProposerPreferences
560
540
  );
561
541
  }
562
542
 
563
543
  private async publishGossip<K extends GossipType>(
564
544
  topic: GossipTopicMap[K],
565
- object: GossipTypeMap[K],
566
- opts?: PublishOpts | undefined
545
+ object: GossipTypeMap[K]
567
546
  ): Promise<number> {
568
547
  const topicStr = stringifyGossipTopic(this.config, topic);
569
548
  const sszType = getGossipSSZType(topic);
570
549
  const messageData = (sszType.serialize as (object: GossipTypeMap[GossipType]) => Uint8Array)(object);
571
- opts = {
572
- ...opts,
550
+ const opts: PublishOpts = {
573
551
  ignoreDuplicatePublishError: gossipTopicIgnoreDuplicatePublishError[topic.type],
552
+ // Leave undefined unless the topic opts out, so `--network.allowPublishToZeroPeers` still applies
553
+ allowPublishToZeroTopicPeers: gossipTopicAllowPublishToZeroPeers[topic.type] ? true : undefined,
574
554
  };
575
555
  const sentPeers = await this.core.publishGossip(topicStr, messageData, opts);
576
556
 
@@ -813,11 +793,7 @@ export class Network implements INetwork {
813
793
  await this.waitForSyncMessageCutoff(finalityUpdate.signatureSlot);
814
794
  await this.publishLightClientFinalityUpdate(finalityUpdate);
815
795
  } catch (e) {
816
- // Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
817
- // Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
818
- if (!isPublishToZeroPeersError(e as Error)) {
819
- this.logger.debug("Error on BeaconGossipHandler.onLightclientFinalityUpdate", {}, e as Error);
820
- }
796
+ this.logger.debug("Error on BeaconGossipHandler.onLightclientFinalityUpdate", {}, e as Error);
821
797
  }
822
798
  };
823
799
 
@@ -830,11 +806,7 @@ export class Network implements INetwork {
830
806
  await this.waitForSyncMessageCutoff(optimisticUpdate.signatureSlot);
831
807
  await this.publishLightClientOptimisticUpdate(optimisticUpdate);
832
808
  } catch (e) {
833
- // Non-mandatory route on most of network as of Oct 2022. May not have found any peers on topic yet
834
- // Remove once https://github.com/ChainSafe/js-libp2p-gossipsub/issues/367
835
- if (!isPublishToZeroPeersError(e as Error)) {
836
- this.logger.debug("Error on BeaconGossipHandler.onLightclientOptimisticUpdate", {}, e as Error);
837
- }
809
+ this.logger.debug("Error on BeaconGossipHandler.onLightclientOptimisticUpdate", {}, e as Error);
838
810
  }
839
811
  };
840
812
 
@@ -23,11 +23,6 @@ export function getConnection(libp2p: Libp2p, peerIdStr: string): Connection | u
23
23
  return getConnectionsMap(libp2p).get(peerIdStr)?.value[0] ?? undefined;
24
24
  }
25
25
 
26
- // https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
27
- export function isPublishToZeroPeersError(e: Error): boolean {
28
- return e.message.includes("PublishError.NoPeersSubscribedToTopic");
29
- }
30
-
31
26
  // https://github.com/libp2p/js-libp2p/blob/f87cba928991736d9646b3e054c367f55cab315c/packages/gossipsub/src/gossipsub.ts#L2076
32
27
  export function isPublishDuplicateError(e: Error): boolean {
33
28
  return e.message.includes("PublishError.Duplicate");
@@ -1,12 +1,13 @@
1
1
  import {setMaxListeners} from "node:events";
2
2
  import {PrivateKey} from "@libp2p/interface";
3
3
  import {Registry} from "prom-client";
4
+ import {type PubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
4
5
  import {hasher} from "@chainsafe/persistent-merkle-tree";
5
6
  import {BeaconApiMethods} from "@lodestar/api/beacon/server";
6
7
  import {BeaconConfig} from "@lodestar/config";
7
8
  import type {LoggerNode} from "@lodestar/logger/node";
8
9
  import {ZERO_HASH_HEX} from "@lodestar/params";
9
- import {IBeaconStateView, PubkeyCache, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
10
+ import {IBeaconStateView, isStatePostBellatrix, isStatePostGloas} from "@lodestar/state-transition";
10
11
  import {phase0} from "@lodestar/types";
11
12
  import {sleep, toRootHex} from "@lodestar/utils";
12
13
  import {ProcessShutdownCallback} from "@lodestar/validator";