@lodestar/beacon-node 1.50.0-dev.335476095e → 1.50.0-dev.3acad901ec

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 (105) hide show
  1. package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
  2. package/lib/api/impl/beacon/state/index.js +23 -3
  3. package/lib/api/impl/beacon/state/index.js.map +1 -1
  4. package/lib/api/impl/debug/index.d.ts.map +1 -1
  5. package/lib/api/impl/debug/index.js +26 -18
  6. package/lib/api/impl/debug/index.js.map +1 -1
  7. package/lib/api/impl/validator/index.d.ts.map +1 -1
  8. package/lib/api/impl/validator/index.js +6 -0
  9. package/lib/api/impl/validator/index.js.map +1 -1
  10. package/lib/chain/archiveStore/archiveStore.d.ts +5 -0
  11. package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
  12. package/lib/chain/archiveStore/archiveStore.js +13 -23
  13. package/lib/chain/archiveStore/archiveStore.js.map +1 -1
  14. package/lib/chain/archiveStore/utils/pruneHistory.d.ts +1 -1
  15. package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
  16. package/lib/chain/archiveStore/utils/pruneHistory.js +2 -1
  17. package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
  18. package/lib/chain/blocks/importBlock.d.ts.map +1 -1
  19. package/lib/chain/blocks/importBlock.js +7 -3
  20. package/lib/chain/blocks/importBlock.js.map +1 -1
  21. package/lib/chain/chain.d.ts +3 -1
  22. package/lib/chain/chain.d.ts.map +1 -1
  23. package/lib/chain/chain.js +13 -5
  24. package/lib/chain/chain.js.map +1 -1
  25. package/lib/chain/interface.d.ts +1 -0
  26. package/lib/chain/interface.d.ts.map +1 -1
  27. package/lib/chain/opPools/aggregatedAttestationPool.d.ts +4 -2
  28. package/lib/chain/opPools/aggregatedAttestationPool.d.ts.map +1 -1
  29. package/lib/chain/opPools/aggregatedAttestationPool.js +30 -8
  30. package/lib/chain/opPools/aggregatedAttestationPool.js.map +1 -1
  31. package/lib/chain/options.d.ts +1 -0
  32. package/lib/chain/options.d.ts.map +1 -1
  33. package/lib/chain/seenCache/seenGossipBlockInput.d.ts +7 -0
  34. package/lib/chain/seenCache/seenGossipBlockInput.d.ts.map +1 -1
  35. package/lib/chain/seenCache/seenGossipBlockInput.js +21 -0
  36. package/lib/chain/seenCache/seenGossipBlockInput.js.map +1 -1
  37. package/lib/chain/shufflingCache.d.ts +1 -0
  38. package/lib/chain/shufflingCache.d.ts.map +1 -1
  39. package/lib/chain/shufflingCache.js +4 -0
  40. package/lib/chain/shufflingCache.js.map +1 -1
  41. package/lib/db/beacon.d.ts +2 -0
  42. package/lib/db/beacon.d.ts.map +1 -1
  43. package/lib/db/beacon.js +3 -0
  44. package/lib/db/beacon.js.map +1 -1
  45. package/lib/db/buckets.d.ts +2 -1
  46. package/lib/db/buckets.d.ts.map +1 -1
  47. package/lib/db/buckets.js +1 -0
  48. package/lib/db/buckets.js.map +1 -1
  49. package/lib/db/interface.d.ts +2 -0
  50. package/lib/db/interface.d.ts.map +1 -1
  51. package/lib/db/repositories/blockArchiveIndex.d.ts +2 -2
  52. package/lib/db/repositories/blockArchiveIndex.d.ts.map +1 -1
  53. package/lib/db/repositories/earliestAvailableSlot.d.ts +11 -0
  54. package/lib/db/repositories/earliestAvailableSlot.d.ts.map +1 -0
  55. package/lib/db/repositories/earliestAvailableSlot.js +19 -0
  56. package/lib/db/repositories/earliestAvailableSlot.js.map +1 -0
  57. package/lib/metrics/metrics/lodestar.d.ts +1 -0
  58. package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
  59. package/lib/metrics/metrics/lodestar.js +4 -0
  60. package/lib/metrics/metrics/lodestar.js.map +1 -1
  61. package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
  62. package/lib/network/processor/gossipHandlers.js +59 -63
  63. package/lib/network/processor/gossipHandlers.js.map +1 -1
  64. package/lib/node/nodejs.d.ts +3 -1
  65. package/lib/node/nodejs.d.ts.map +1 -1
  66. package/lib/node/nodejs.js +2 -1
  67. package/lib/node/nodejs.js.map +1 -1
  68. package/lib/sync/range/batch.d.ts.map +1 -1
  69. package/lib/sync/range/batch.js +1 -0
  70. package/lib/sync/range/batch.js.map +1 -1
  71. package/lib/sync/range/chain.d.ts.map +1 -1
  72. package/lib/sync/range/chain.js +1 -0
  73. package/lib/sync/range/chain.js.map +1 -1
  74. package/lib/sync/unknownBlock.d.ts +5 -2
  75. package/lib/sync/unknownBlock.d.ts.map +1 -1
  76. package/lib/sync/unknownBlock.js +44 -20
  77. package/lib/sync/unknownBlock.js.map +1 -1
  78. package/lib/sync/utils/downloadByRange.d.ts +8 -0
  79. package/lib/sync/utils/downloadByRange.d.ts.map +1 -1
  80. package/lib/sync/utils/downloadByRange.js +32 -9
  81. package/lib/sync/utils/downloadByRange.js.map +1 -1
  82. package/package.json +16 -16
  83. package/src/api/impl/beacon/state/index.ts +31 -3
  84. package/src/api/impl/debug/index.ts +28 -19
  85. package/src/api/impl/validator/index.ts +8 -0
  86. package/src/chain/archiveStore/archiveStore.ts +18 -24
  87. package/src/chain/archiveStore/utils/pruneHistory.ts +4 -1
  88. package/src/chain/blocks/importBlock.ts +9 -3
  89. package/src/chain/chain.ts +26 -4
  90. package/src/chain/interface.ts +1 -0
  91. package/src/chain/opPools/aggregatedAttestationPool.ts +34 -10
  92. package/src/chain/options.ts +1 -1
  93. package/src/chain/seenCache/seenGossipBlockInput.ts +23 -0
  94. package/src/chain/shufflingCache.ts +5 -0
  95. package/src/db/beacon.ts +3 -0
  96. package/src/db/buckets.ts +1 -0
  97. package/src/db/interface.ts +2 -0
  98. package/src/db/repositories/earliestAvailableSlot.ts +19 -0
  99. package/src/metrics/metrics/lodestar.ts +4 -0
  100. package/src/network/processor/gossipHandlers.ts +90 -76
  101. package/src/node/nodejs.ts +4 -0
  102. package/src/sync/range/batch.ts +1 -0
  103. package/src/sync/range/chain.ts +1 -0
  104. package/src/sync/unknownBlock.ts +57 -21
  105. package/src/sync/utils/downloadByRange.ts +47 -8
@@ -73,7 +73,11 @@ type AdvancePendingBlockResult =
73
73
  | "removed";
74
74
 
75
75
  class UnknownBlockRateLimitedError extends Error {
76
- constructor(message: string) {
76
+ constructor(
77
+ message: string,
78
+ /** Earliest backoff expiry seen when the download was deferred, it is retried once that has passed */
79
+ readonly retryAt: number
80
+ ) {
77
81
  super(message);
78
82
  this.name = "UnknownBlockRateLimitedError";
79
83
  }
@@ -849,17 +853,20 @@ export class BlockInputSync {
849
853
  }
850
854
  };
851
855
 
852
- private scheduleRateLimitBackoffRetry(): void {
856
+ /**
857
+ * Returns false if there is no backoff left to wait for, in which case no retry is scheduled.
858
+ */
859
+ private scheduleRateLimitBackoffRetry(): boolean {
853
860
  this.clearRateLimitBackoffTimer();
854
861
 
855
862
  if (!this.subscribedToNetworkEvents || (this.pendingBlocks.size === 0 && this.pendingPayloads.size === 0)) {
856
- return;
863
+ return false;
857
864
  }
858
865
 
859
866
  const now = Date.now();
860
867
  const retryAt = this.peerBalancer.getNextRateLimitRetryAt();
861
868
  if (retryAt === null) {
862
- return;
869
+ return false;
863
870
  }
864
871
 
865
872
  this.rateLimitBackoffTimeout = setTimeout(
@@ -870,6 +877,7 @@ export class BlockInputSync {
870
877
  },
871
878
  Math.max(0, retryAt - now)
872
879
  );
880
+ return true;
873
881
  }
874
882
 
875
883
  private clearRateLimitBackoffTimer(): void {
@@ -1081,7 +1089,11 @@ export class BlockInputSync {
1081
1089
  pendingBlock.status = PendingBlockInputStatus.pending;
1082
1090
  }
1083
1091
  this.logger.debug("Deferring unknown block download due to peer rate limit", logCtx, res.err);
1084
- this.scheduleRateLimitBackoffRetry();
1092
+ if (!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now()) {
1093
+ // The backoff this download waited for expired in the meantime, a timer is only armed if another
1094
+ // peer is still in backoff and would retry it later than needed
1095
+ this.triggerUnknownBlockSearch();
1096
+ }
1085
1097
  return;
1086
1098
  }
1087
1099
 
@@ -1364,6 +1376,14 @@ export class BlockInputSync {
1364
1376
  if (!isPendingPayloadEnvelope(payload)) {
1365
1377
  payload.status = PendingPayloadInputStatus.pending;
1366
1378
  }
1379
+ if (
1380
+ res.err instanceof UnknownBlockRateLimitedError &&
1381
+ (!this.scheduleRateLimitBackoffRetry() || res.err.retryAt <= Date.now())
1382
+ ) {
1383
+ // The backoff this download waited for expired in the meantime, a timer is only armed if another
1384
+ // peer is still in backoff and would retry it later than needed
1385
+ this.triggerUnknownBlockSearch();
1386
+ }
1367
1387
  }
1368
1388
 
1369
1389
  private async processPayload(pendingPayload: PendingPayloadInput): Promise<void> {
@@ -1488,16 +1508,21 @@ export class BlockInputSync {
1488
1508
  const pendingColumns = payloadInput?.hasAllData()
1489
1509
  ? new Set<number>()
1490
1510
  : new Set(payloadInput?.getMissingSampledColumnMeta().missing ?? []);
1511
+ // Same `now` for both calls, a backoff expiring in between would be reported as no peer available
1512
+ const now = Date.now();
1491
1513
  // prefer peers that gossiped this payload root to us (#9923)
1492
1514
  const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1493
1515
  pendingColumns,
1494
1516
  excludedPeers,
1495
- cacheItem.peerIdStrings
1517
+ cacheItem.peerIdStrings,
1518
+ now
1496
1519
  );
1497
1520
  if (peerMeta === null) {
1498
- if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1521
+ const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
1522
+ if (retryAt !== null) {
1499
1523
  throw new UnknownBlockRateLimitedError(
1500
- `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
1524
+ `Error fetching payload by root slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
1525
+ retryAt
1501
1526
  );
1502
1527
  }
1503
1528
 
@@ -1636,9 +1661,11 @@ export class BlockInputSync {
1636
1661
  );
1637
1662
  this.metrics?.blockInputSync.payloadFetchPeers.set({result: FetchResult.FailureMaxAttempts}, i - 1);
1638
1663
 
1639
- if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
1664
+ const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
1665
+ if (retryAt !== null) {
1640
1666
  throw new UnknownBlockRateLimitedError(
1641
- `Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`
1667
+ `Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts: peers are rate-limited`,
1668
+ retryAt
1642
1669
  );
1643
1670
  }
1644
1671
  throw Error(`Error fetching payload with slot=${slot} root=${rootHex} after ${i - 1} attempts.`);
@@ -1730,16 +1757,21 @@ export class BlockInputSync {
1730
1757
  isPendingBlockInput(cacheItem) && isBlockInputColumns(cacheItem.blockInput)
1731
1758
  ? new Set(cacheItem.blockInput.getMissingSampledColumnMeta().missing)
1732
1759
  : defaultPendingColumns;
1760
+ // Same `now` for both calls, a backoff expiring in between would be reported as no peer available
1761
+ const now = Date.now();
1733
1762
  // prefer peers that gossiped this block root to us (#9923)
1734
1763
  const peerMeta = this.peerBalancer.bestPeerForPendingColumns(
1735
1764
  pendingColumns,
1736
1765
  excludedPeers,
1737
- cacheItem.peerIdStrings
1766
+ cacheItem.peerIdStrings,
1767
+ now
1738
1768
  );
1739
1769
  if (peerMeta === null) {
1740
- if (this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers) !== null) {
1770
+ const retryAt = this.peerBalancer.getNextRateLimitRetryAt(pendingColumns, excludedPeers, now);
1771
+ if (retryAt !== null) {
1741
1772
  throw new UnknownBlockRateLimitedError(
1742
- `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`
1773
+ `Error fetching UnknownBlockRoot slot=${slot} root=${rootHex} after ${i}: peers with needed columns are rate-limited`,
1774
+ retryAt
1743
1775
  );
1744
1776
  }
1745
1777
 
@@ -1845,8 +1877,9 @@ export class BlockInputSync {
1845
1877
 
1846
1878
  const message = `Error fetching BlockInput with slot=${slot} root=${rootHex} after ${i - 1} attempts.`;
1847
1879
 
1848
- if (deferredByRateLimit && this.peerBalancer.getNextRateLimitRetryAt() !== null) {
1849
- throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`);
1880
+ const retryAt = deferredByRateLimit ? this.peerBalancer.getNextRateLimitRetryAt() : null;
1881
+ if (retryAt !== null) {
1882
+ throw new UnknownBlockRateLimitedError(`${message} Peers are rate-limited.`, retryAt);
1850
1883
  }
1851
1884
 
1852
1885
  if (!isPendingBlockInput(cacheItem)) {
@@ -2037,8 +2070,11 @@ export class UnknownBlockPeerBalancer {
2037
2070
  this.rateLimitedUntilByPeer.set(peerId, rateLimitedUntilMs);
2038
2071
  }
2039
2072
 
2040
- getNextRateLimitRetryAt(pendingColumns?: Set<number>, excludedPeers?: Set<PeerIdStr>): number | null {
2041
- const now = Date.now();
2073
+ getNextRateLimitRetryAt(
2074
+ pendingColumns?: Set<number>,
2075
+ excludedPeers?: Set<PeerIdStr>,
2076
+ now = Date.now()
2077
+ ): number | null {
2042
2078
  let retryAt: number | null = null;
2043
2079
 
2044
2080
  for (const [peerId, rateLimitedUntil] of this.rateLimitedUntilByPeer.entries()) {
@@ -2077,9 +2113,10 @@ export class UnknownBlockPeerBalancer {
2077
2113
  bestPeerForPendingColumns(
2078
2114
  pendingColumns: Set<number>,
2079
2115
  excludedPeers: Set<PeerIdStr>,
2080
- preferredPeers?: Set<PeerIdStr>
2116
+ preferredPeers?: Set<PeerIdStr>,
2117
+ now = Date.now()
2081
2118
  ): PeerSyncMeta | null {
2082
- const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers);
2119
+ const eligiblePeers = this.filterPeers(pendingColumns, excludedPeers, now);
2083
2120
  if (eligiblePeers.length === 0) {
2084
2121
  return null;
2085
2122
  }
@@ -2120,8 +2157,7 @@ export class UnknownBlockPeerBalancer {
2120
2157
  return totalActiveRequests;
2121
2158
  }
2122
2159
 
2123
- private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>): PeerIdStr[] {
2124
- const now = Date.now();
2160
+ private filterPeers(pendingDataColumns: Set<number>, excludedPeers: Set<PeerIdStr>, now: number): PeerIdStr[] {
2125
2161
  let maxColumnCount = 0;
2126
2162
  const considerPeers: {peerId: PeerIdStr; columnCount: number}[] = [];
2127
2163
  for (const [peerId, syncMeta] of this.peersMeta.entries()) {
@@ -16,6 +16,7 @@ import {
16
16
  deneb,
17
17
  fulu,
18
18
  gloas,
19
+ isGloasBeaconBlock,
19
20
  isGloasDataColumnSidecar,
20
21
  phase0,
21
22
  } from "@lodestar/types";
@@ -62,6 +63,7 @@ export type DownloadByRangeRequests = {
62
63
  export type ParentPayloadCommitments = {
63
64
  blockRoot: Uint8Array;
64
65
  blockRootHex: RootHex;
66
+ blockHash: Uint8Array;
65
67
  kzgCommitments: deneb.BlobKzgCommitments;
66
68
  };
67
69
 
@@ -1171,6 +1173,8 @@ export enum DownloadByRangeErrorCode {
1171
1173
 
1172
1174
  /** Envelope beaconBlockRoot does not match the block's root */
1173
1175
  INVALID_ENVELOPE_BEACON_BLOCK_ROOT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BEACON_BLOCK_ROOT",
1176
+ /** Envelope payload.blockHash does not match the block's bid */
1177
+ INVALID_ENVELOPE_BLOCK_HASH = "DOWNLOAD_BY_RANGE_ERROR_INVALID_ENVELOPE_BLOCK_HASH",
1174
1178
 
1175
1179
  /** Block segment + envelopes failed chain-segment linearity / FULL-chain checks */
1176
1180
  INVALID_CHAIN_SEGMENT = "DOWNLOAD_BY_RANGE_ERROR_INVALID_CHAIN_SEGMENT",
@@ -1272,6 +1276,12 @@ export type DownloadByRangeErrorType =
1272
1276
  expected: string;
1273
1277
  actual: string;
1274
1278
  }
1279
+ | {
1280
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH;
1281
+ slot: Slot;
1282
+ expected: string;
1283
+ actual: string;
1284
+ }
1275
1285
  | {
1276
1286
  code: DownloadByRangeErrorCode.INVALID_CHAIN_SEGMENT;
1277
1287
  slot: Slot;
@@ -1295,47 +1305,76 @@ export function validateEnvelopesByRangeResponse(
1295
1305
  payloadEnvelopes: gloas.SignedExecutionPayloadEnvelope[],
1296
1306
  parentPayloadCommitments?: ParentPayloadCommitments
1297
1307
  ): Map<Slot, gloas.SignedExecutionPayloadEnvelope> {
1298
- // Build a map of slot -> blockRoot for all blocks in the batch
1299
- const batchBlockRoots = new Map<Slot, Uint8Array>();
1308
+ // Build a map of slot -> block root and bid block hash for all blocks in the batch
1309
+ const batchBlocksBySlot = new Map<Slot, {blockRoot: Uint8Array; block: SignedBeaconBlock | null}>();
1300
1310
  if (batchBlocks) {
1301
1311
  for (const blockInput of batchBlocks) {
1302
- batchBlockRoots.set(blockInput.slot, fromHex(blockInput.blockRootHex));
1312
+ batchBlocksBySlot.set(blockInput.slot, {
1313
+ blockRoot: fromHex(blockInput.blockRootHex),
1314
+ block: blockInput.hasBlock() ? blockInput.getBlock() : null,
1315
+ });
1303
1316
  }
1304
1317
  }
1305
1318
  for (const {block, blockRoot} of validatedBlocks) {
1306
- batchBlockRoots.set(block.message.slot, blockRoot);
1319
+ batchBlocksBySlot.set(block.message.slot, {blockRoot, block});
1307
1320
  }
1308
1321
 
1309
1322
  const payloadEnvelopeMap = new Map<Slot, gloas.SignedExecutionPayloadEnvelope>();
1310
1323
 
1311
1324
  for (const payloadEnvelope of payloadEnvelopes) {
1312
1325
  const slot = payloadEnvelope.message.payload.slotNumber;
1313
- const batchBlockRoot = batchBlockRoots.get(slot);
1326
+ const batchBlock = batchBlocksBySlot.get(slot);
1314
1327
 
1315
- if (batchBlockRoot === undefined) {
1328
+ if (batchBlock === undefined) {
1316
1329
  // Keep the requested dangling-parent envelope only when its beaconBlockRoot matches
1317
1330
  // exactly. All other unrelated envelopes are dropped.
1318
1331
  if (
1319
1332
  parentPayloadCommitments !== undefined &&
1320
1333
  byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, parentPayloadCommitments.blockRoot)
1321
1334
  ) {
1335
+ assertEnvelopeBlockHash(payloadEnvelope, parentPayloadCommitments.blockHash);
1322
1336
  payloadEnvelopeMap.set(slot, payloadEnvelope);
1323
1337
  }
1324
1338
  continue;
1325
1339
  }
1326
1340
 
1327
1341
  // Verify beaconBlockRoot matches the block's root
1328
- if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlockRoot)) {
1342
+ if (!byteArrayEquals(payloadEnvelope.message.beaconBlockRoot, batchBlock.blockRoot)) {
1329
1343
  throw new DownloadByRangeError({
1330
1344
  code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BEACON_BLOCK_ROOT,
1331
1345
  slot,
1332
- expected: toRootHex(batchBlockRoot),
1346
+ expected: toRootHex(batchBlock.blockRoot),
1333
1347
  actual: toRootHex(payloadEnvelope.message.beaconBlockRoot),
1334
1348
  });
1335
1349
  }
1336
1350
 
1351
+ if (batchBlock.block !== null && isGloasBeaconBlock(batchBlock.block.message)) {
1352
+ assertEnvelopeBlockHash(
1353
+ payloadEnvelope,
1354
+ batchBlock.block.message.body.signedExecutionPayloadBid.message.blockHash
1355
+ );
1356
+ }
1357
+
1337
1358
  payloadEnvelopeMap.set(slot, payloadEnvelope);
1338
1359
  }
1339
1360
 
1340
1361
  return payloadEnvelopeMap;
1341
1362
  }
1363
+
1364
+ /**
1365
+ * Rejected before it is cached. In-batch, `assertLinearChainSegment` fails on a wrong hash without evicting the
1366
+ * envelope. For the dangling parent, the import evicts the parent's entry and range sync cannot recreate it.
1367
+ */
1368
+ function assertEnvelopeBlockHash(
1369
+ payloadEnvelope: gloas.SignedExecutionPayloadEnvelope,
1370
+ bidBlockHash: Uint8Array
1371
+ ): void {
1372
+ if (!byteArrayEquals(payloadEnvelope.message.payload.blockHash, bidBlockHash)) {
1373
+ throw new DownloadByRangeError({
1374
+ code: DownloadByRangeErrorCode.INVALID_ENVELOPE_BLOCK_HASH,
1375
+ slot: payloadEnvelope.message.payload.slotNumber,
1376
+ expected: toRootHex(bidBlockHash),
1377
+ actual: toRootHex(payloadEnvelope.message.payload.blockHash),
1378
+ });
1379
+ }
1380
+ }