Skip to content

Commit 1e596a7

Browse files
authored
fix: validate MIN_EPOCHS for blob/data column by range/root requests (#9173)
Add `MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS` validation to `blobSidecarsByRange` and `blobSidecarsByRoot` and `MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS` validation to `dataColumnSidecarsByRange`. Respond with `RESOURCE_UNAVAILABLE` for requests before the minimum epoch per spec. For blocks and execution payload envelopes, retain historical serving to support genesis sync and archival peers. Remove the pre-existing epoch floor from `executionPayloadEnvelopesByRoot` to align with this approach. Closes #9107
1 parent d4d0b21 commit 1e596a7

7 files changed

Lines changed: 57 additions & 20 deletions

File tree

packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRange.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,13 @@ export function validateBeaconBlocksByRangeRequest(
9696
if (count < 1) {
9797
throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1");
9898
}
99-
// TODO: validate against MIN_EPOCHS_FOR_BLOCK_REQUESTS
10099
if (startSlot < GENESIS_SLOT) {
101100
throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis");
102101
}
103102

103+
// The phase0 req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
104+
// Archival nodes may still serve older retained blocks to allow genesis sync.
105+
104106
// step > 1 is deprecated, see https://github.com/ethereum/consensus-specs/pull/2856
105107

106108
const maxRequestBlocks = isForkPostDeneb(config.getForkName(startSlot))

packages/beacon-node/src/network/reqresp/handlers/beaconBlocksByRoot.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ export async function* onBeaconBlocksByRoot(
88
requestBody: BeaconBlocksByRootRequest,
99
chain: IBeaconChain
1010
): AsyncIterable<ResponseOutgoing> {
11+
// The phase0 req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
12+
// Archival nodes may still serve older retained blocks to allow genesis sync.
13+
1114
for (const blockRoot of requestBody) {
1215
const root = blockRoot;
1316
const block = await chain.getSerializedBlockByRoot(toRootHex(root));

packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRange.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {ChainConfig} from "@lodestar/config";
22
import {BLOB_SIDECAR_FIXED_SIZE, GENESIS_SLOT} from "@lodestar/params";
33
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
44
import {computeEpochAtSlot} from "@lodestar/state-transition";
5-
import {Slot, deneb} from "@lodestar/types";
5+
import {Epoch, Slot, deneb} from "@lodestar/types";
66
import {fromHex} from "@lodestar/utils";
77
import {IBeaconChain} from "../../../chain/index.js";
88
import {IBeaconDb} from "../../../db/index.js";
@@ -14,7 +14,7 @@ export async function* onBlobSidecarsByRange(
1414
db: IBeaconDb
1515
): AsyncIterable<ResponseOutgoing> {
1616
// Non-finalized range of blobs
17-
const {startSlot, count} = validateBlobSidecarsByRangeRequest(chain.config, request);
17+
const {startSlot, count} = validateBlobSidecarsByRangeRequest(chain.config, chain.clock.currentEpoch, request);
1818
const endSlot = startSlot + count;
1919

2020
const finalized = db.blobSidecarsArchive;
@@ -94,6 +94,7 @@ export function* iterateBlobBytesFromWrapper(
9494

9595
export function validateBlobSidecarsByRangeRequest(
9696
config: ChainConfig,
97+
currentEpoch: Epoch,
9798
request: deneb.BlobSidecarsByRangeRequest
9899
): deneb.BlobSidecarsByRangeRequest {
99100
const {startSlot} = request;
@@ -102,11 +103,22 @@ export function validateBlobSidecarsByRangeRequest(
102103
if (count < 1) {
103104
throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1");
104105
}
105-
// TODO: validate against MIN_EPOCHS_FOR_BLOCK_REQUESTS
106106
if (startSlot < GENESIS_SLOT) {
107107
throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis");
108108
}
109109

110+
// Spec: [max(current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH), current_epoch]
111+
const minimumRequestEpoch = Math.max(
112+
currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS,
113+
config.DENEB_FORK_EPOCH
114+
);
115+
if (computeEpochAtSlot(startSlot) < minimumRequestEpoch) {
116+
throw new ResponseError(
117+
RespStatus.RESOURCE_UNAVAILABLE,
118+
"startSlot is before MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS"
119+
);
120+
}
121+
110122
if (count > config.MAX_REQUEST_BLOCKS_DENEB) {
111123
count = config.MAX_REQUEST_BLOCKS_DENEB;
112124
}

packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ export async function* onBlobSidecarsByRoot(
1212
): AsyncIterable<ResponseOutgoing> {
1313
const finalizedSlot = chain.forkChoice.getFinalizedBlock().slot;
1414

15+
// Spec: [max(current_epoch - MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS, DENEB_FORK_EPOCH), current_epoch]
16+
const currentEpoch = chain.clock.currentEpoch;
17+
const minimumRequestEpoch = Math.max(
18+
currentEpoch - chain.config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS,
19+
chain.config.DENEB_FORK_EPOCH
20+
);
21+
1522
// In sidecars by root request, it can be expected that sidecar requests will be come
1623
// clustured by blockroots, and this helps us save db lookups once we load sidecars
1724
// for a root
@@ -29,6 +36,10 @@ export async function* onBlobSidecarsByRoot(
2936
continue;
3037
}
3138

39+
if (computeEpochAtSlot(block.slot) < minimumRequestEpoch) {
40+
continue;
41+
}
42+
3243
// Check if we need to load sidecars for a new block root
3344
if (lastFetchedSideCars === null || lastFetchedSideCars.blockRoot !== blockRootHex) {
3445
const blobSidecarsBytes = await chain.getSerializedBlobSidecars(block.slot, blockRootHex);

packages/beacon-node/src/network/reqresp/handlers/dataColumnSidecarsByRange.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {ChainConfig} from "@lodestar/config";
33
import {GENESIS_SLOT} from "@lodestar/params";
44
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
55
import {computeEpochAtSlot} from "@lodestar/state-transition";
6-
import {ColumnIndex, fulu} from "@lodestar/types";
6+
import {ColumnIndex, Epoch, fulu} from "@lodestar/types";
77
import {fromHex} from "@lodestar/utils";
88
import {IBeaconChain} from "../../../chain/index.js";
99
import {IBeaconDb} from "../../../db/index.js";
@@ -21,7 +21,11 @@ export async function* onDataColumnSidecarsByRange(
2121
peerClient: string
2222
): AsyncIterable<ResponseOutgoing> {
2323
// Non-finalized range of columns
24-
const {startSlot, count, columns: requestedColumns} = validateDataColumnSidecarsByRangeRequest(chain.config, request);
24+
const {
25+
startSlot,
26+
count,
27+
columns: requestedColumns,
28+
} = validateDataColumnSidecarsByRangeRequest(chain.config, chain.clock.currentEpoch, request);
2529
const availableColumns = validateRequestedDataColumns(chain, requestedColumns);
2630
const endSlot = startSlot + count;
2731

@@ -139,6 +143,7 @@ export async function* onDataColumnSidecarsByRange(
139143

140144
export function validateDataColumnSidecarsByRangeRequest(
141145
config: ChainConfig,
146+
currentEpoch: Epoch,
142147
request: fulu.DataColumnSidecarsByRangeRequest
143148
): fulu.DataColumnSidecarsByRangeRequest {
144149
const {startSlot, columns} = request;
@@ -147,11 +152,22 @@ export function validateDataColumnSidecarsByRangeRequest(
147152
if (count < 1) {
148153
throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1");
149154
}
150-
// TODO: validate against MIN_EPOCHS_FOR_BLOCK_REQUESTS
151155
if (startSlot < GENESIS_SLOT) {
152156
throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis");
153157
}
154158

159+
// Spec: [max(current_epoch - MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS, FULU_FORK_EPOCH), current_epoch]
160+
const minimumRequestEpoch = Math.max(
161+
currentEpoch - config.MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS,
162+
config.FULU_FORK_EPOCH
163+
);
164+
if (computeEpochAtSlot(startSlot) < minimumRequestEpoch) {
165+
throw new ResponseError(
166+
RespStatus.RESOURCE_UNAVAILABLE,
167+
"startSlot is before MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS"
168+
);
169+
}
170+
155171
if (count > config.MAX_REQUEST_BLOCKS_DENEB) {
156172
count = config.MAX_REQUEST_BLOCKS_DENEB;
157173
}

packages/beacon-node/src/network/reqresp/handlers/executionPayloadEnvelopesByRange.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,13 @@ export function validateExecutionPayloadEnvelopesByRangeRequest(
8181
if (count < 1) {
8282
throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1");
8383
}
84-
// TODO: validate against MIN_EPOCHS_FOR_BLOCK_REQUESTS
8584
if (startSlot < GENESIS_SLOT) {
8685
throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis");
8786
}
8887

88+
// The gloas req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
89+
// Archival nodes may still serve older retained payloads to allow genesis sync.
90+
8991
if (count > config.MAX_REQUEST_BLOCKS_DENEB) {
9092
count = config.MAX_REQUEST_BLOCKS_DENEB;
9193
}

packages/beacon-node/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@ export async function* onExecutionPayloadEnvelopesByRoot(
1010
chain: IBeaconChain,
1111
db: IBeaconDb
1212
): AsyncIterable<ResponseOutgoing> {
13-
// Spec: [max(GLOAS_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]
14-
const currentEpoch = chain.clock.currentEpoch;
15-
const minimumRequestEpoch = Math.max(
16-
currentEpoch - chain.config.MIN_EPOCHS_FOR_BLOCK_REQUESTS,
17-
chain.config.GLOAS_FORK_EPOCH
18-
);
13+
// The gloas req/resp spec uses MIN_EPOCHS_FOR_BLOCK_REQUESTS to define the minimum range peers MUST serve.
14+
// Archival nodes may still serve older retained payloads to allow genesis sync.
1915

2016
for (const root of requestBody) {
2117
const rootHex = toRootHex(root);
@@ -27,16 +23,11 @@ export async function* onExecutionPayloadEnvelopesByRoot(
2723
continue;
2824
}
2925

30-
const requestedEpoch = computeEpochAtSlot(slot);
31-
if (requestedEpoch < minimumRequestEpoch) {
32-
continue;
33-
}
34-
3526
const envelopeBytes = await chain.getSerializedExecutionPayloadEnvelope(slot, rootHex);
3627
if (envelopeBytes) {
3728
yield {
3829
data: envelopeBytes,
39-
boundary: chain.config.getForkBoundaryAtEpoch(requestedEpoch),
30+
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(slot)),
4031
};
4132
}
4233
}

0 commit comments

Comments
 (0)