Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,24 @@ export class BeaconChain implements IBeaconChain {
return null;
}

async getSerializedExecutionPayloadEnvelope(blockSlot: Slot, blockRootHex: string): Promise<Uint8Array | null> {
const payloadInput = this.seenPayloadEnvelopeInputCache.get(blockRootHex);
if (payloadInput?.hasPayloadEnvelope()) {
const envelope = payloadInput.getPayloadEnvelope();
const serialized = this.serializedCache.get(envelope);
if (serialized) {
return serialized;
}
return ssz.gloas.SignedExecutionPayloadEnvelope.serialize(envelope);
}

return (
(await this.db.executionPayloadEnvelope.getBinary(fromHex(blockRootHex))) ??
(await this.db.executionPayloadEnvelopeArchive.getBinary(blockSlot)) ??
null
);
}

async getDataColumnSidecars(blockSlot: Slot, blockRootHex: string): Promise<DataColumnSidecars> {
const blockInput = this.seenBlockInputCache.get(blockRootHex);
if (blockInput) {
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export interface IBeaconChain {
blockRootHex: string,
indices: number[]
): Promise<(Uint8Array | undefined)[]>;
getSerializedExecutionPayloadEnvelope(blockSlot: Slot, blockRootHex: string): Promise<Uint8Array | null>;

produceCommonBlockBody(blockAttributes: BlockAttributes): Promise<CommonBlockBody>;
produceBlock(blockAttributes: BlockAttributes & {commonBlockBodyPromise: Promise<CommonBlockBody>}): Promise<{
Expand Down
15 changes: 14 additions & 1 deletion packages/beacon-node/src/network/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ import {
import {BlockInputSource} from "../chain/blocks/blockInput/types.js";
import {CustodyConfig} from "../util/dataColumns.js";
import {PeerIdStr} from "../util/peerId.js";
import {BeaconBlocksByRootRequest, BlobSidecarsByRootRequest, DataColumnSidecarsByRootRequest} from "../util/types.js";
import {
BeaconBlocksByRootRequest,
BlobSidecarsByRootRequest,
DataColumnSidecarsByRootRequest,
ExecutionPayloadEnvelopesByRootRequest,
} from "../util/types.js";
import {INetworkCorePublic} from "./core/types.js";
import {INetworkEventBus} from "./events.js";
import {GossipType} from "./gossip/interface.js";
Expand Down Expand Up @@ -82,6 +87,14 @@ export interface INetwork extends INetworkCorePublic {
peerId: PeerIdStr,
request: DataColumnSidecarsByRootRequest
): Promise<fulu.DataColumnSidecar[]>;
sendExecutionPayloadEnvelopesByRange(
peerId: PeerIdStr,
request: gloas.ExecutionPayloadEnvelopesByRangeRequest
): Promise<gloas.SignedExecutionPayloadEnvelope[]>;
sendExecutionPayloadEnvelopesByRoot(
peerId: PeerIdStr,
request: ExecutionPayloadEnvelopesByRootRequest
): Promise<gloas.SignedExecutionPayloadEnvelope[]>;

// Gossip
publishBeaconBlock(signedBlock: SignedBeaconBlock): Promise<number>;
Expand Down
30 changes: 29 additions & 1 deletion packages/beacon-node/src/network/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ import {IClock} from "../util/clock.js";
import {CustodyConfig} from "../util/dataColumns.js";
import {PeerIdStr, peerIdToString} from "../util/peerId.js";
import {promiseAllMaybeAsync} from "../util/promises.js";
import {BeaconBlocksByRootRequest, BlobSidecarsByRootRequest, DataColumnSidecarsByRootRequest} from "../util/types.js";
import {
BeaconBlocksByRootRequest,
BlobSidecarsByRootRequest,
DataColumnSidecarsByRootRequest,
ExecutionPayloadEnvelopesByRootRequest,
} from "../util/types.js";
import {INetworkCore, NetworkCore, WorkerNetworkCore} from "./core/index.js";
import {INetworkEventBus, NetworkEvent, NetworkEventBus, NetworkEventData} from "./events.js";
import {getActiveForkBoundaries} from "./forks.js";
Expand Down Expand Up @@ -636,6 +641,29 @@ export class Network implements INetwork {
);
}

async sendExecutionPayloadEnvelopesByRange(
peerId: PeerIdStr,
request: gloas.ExecutionPayloadEnvelopesByRangeRequest
): Promise<gloas.SignedExecutionPayloadEnvelope[]> {
return collectMaxResponseTyped(
this.sendReqRespRequest(peerId, ReqRespMethod.ExecutionPayloadEnvelopesByRange, [Version.V1], request),
request.count,
responseSszTypeByMethod[ReqRespMethod.ExecutionPayloadEnvelopesByRange]
Comment thread
wemeetagain marked this conversation as resolved.
);
}

async sendExecutionPayloadEnvelopesByRoot(
peerId: PeerIdStr,
request: ExecutionPayloadEnvelopesByRootRequest
): Promise<gloas.SignedExecutionPayloadEnvelope[]> {
return collectMaxResponseTyped(
this.sendReqRespRequest(peerId, ReqRespMethod.ExecutionPayloadEnvelopesByRoot, [Version.V1], request),
request.length,
responseSszTypeByMethod[ReqRespMethod.ExecutionPayloadEnvelopesByRoot],
this.chain.serializedCache
);
}

private sendReqRespRequest<Req>(
peerId: PeerIdStr,
method: ReqRespMethod,
Expand Down
13 changes: 13 additions & 0 deletions packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,19 @@ export class ReqRespBeaconNode extends ReqResp {
);
}

if (ForkSeq[fork] >= ForkSeq.gloas) {
protocolsAtFork.push(
[
protocols.ExecutionPayloadEnvelopesByRoot(fork, this.config),
this.getHandler(ReqRespMethod.ExecutionPayloadEnvelopesByRoot),
],
[
protocols.ExecutionPayloadEnvelopesByRange(fork, this.config),
this.getHandler(ReqRespMethod.ExecutionPayloadEnvelopesByRange),
]
);
}

return protocolsAtFork;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {ChainConfig} from "@lodestar/config";
import {PayloadStatus} from "@lodestar/fork-choice";
import {GENESIS_SLOT} from "@lodestar/params";
import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp";
import {computeEpochAtSlot} from "@lodestar/state-transition";
import {gloas} from "@lodestar/types";
import {IBeaconChain} from "../../../chain/index.js";
import {IBeaconDb} from "../../../db/index.js";

export async function* onExecutionPayloadEnvelopesByRange(
request: gloas.ExecutionPayloadEnvelopesByRangeRequest,
chain: IBeaconChain,
db: IBeaconDb
): AsyncIterable<ResponseOutgoing> {
const {startSlot, count} = validateExecutionPayloadEnvelopesByRangeRequest(chain.config, request);
Comment thread
ensi321 marked this conversation as resolved.
const endSlot = startSlot + count;

Comment thread
wemeetagain marked this conversation as resolved.
if (startSlot < chain.earliestAvailableSlot) {
return;
}

const finalized = db.executionPayloadEnvelopeArchive;
const finalizedSlot = chain.forkChoice.getFinalizedCheckpointSlot();

// Finalized range of envelopes
if (startSlot <= finalizedSlot) {
for await (const {key, value: envelopeBytes} of finalized.binaryEntriesStream({
gte: startSlot,
lt: endSlot,
})) {
const slot = finalized.decodeKey(key);
yield {
data: envelopeBytes,
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(slot)),
};
}
}

// Non-finalized range of envelopes
if (endSlot > finalizedSlot) {
const headBlock = chain.forkChoice.getHead();
const headRoot = headBlock.blockRoot;
const headChain = chain.forkChoice.getAllAncestorBlocks(headRoot, headBlock.payloadStatus);

// Iterate head chain with ascending block numbers
for (let i = headChain.length - 1; i >= 0; i--) {
const block = headChain[i];

if (block.slot >= startSlot && block.slot < endSlot) {
// Skip EMPTY blocks
if (block.payloadStatus !== PayloadStatus.FULL) {
continue;
}

const envelopeBytes = await chain.getSerializedExecutionPayloadEnvelope(block.slot, block.blockRoot);
Comment thread
ensi321 marked this conversation as resolved.
if (!envelopeBytes) {
throw new ResponseError(
RespStatus.SERVER_ERROR,
`No envelope for root ${block.blockRoot} slot ${block.slot}, startSlot=${startSlot} endSlot=${endSlot} finalizedSlot=${finalizedSlot}`
);
}

yield {
data: envelopeBytes,
boundary: chain.config.getForkBoundaryAtEpoch(computeEpochAtSlot(block.slot)),
};
} else if (block.slot >= endSlot) {
break;
}
}
}
}

export function validateExecutionPayloadEnvelopesByRangeRequest(
config: ChainConfig,
request: gloas.ExecutionPayloadEnvelopesByRangeRequest
): gloas.ExecutionPayloadEnvelopesByRangeRequest {
const {startSlot} = request;
let {count} = request;

if (count < 1) {
throw new ResponseError(RespStatus.INVALID_REQUEST, "count < 1");
}
// TODO: validate against MIN_EPOCHS_FOR_BLOCK_REQUESTS
if (startSlot < GENESIS_SLOT) {
throw new ResponseError(RespStatus.INVALID_REQUEST, "startSlot < genesis");
}

if (count > config.MAX_REQUEST_BLOCKS_DENEB) {
count = config.MAX_REQUEST_BLOCKS_DENEB;
Comment thread
twoeths marked this conversation as resolved.
}

return {startSlot, count};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {ResponseOutgoing} from "@lodestar/reqresp";
import {computeEpochAtSlot} from "@lodestar/state-transition";
import {toRootHex} from "@lodestar/utils";
import {IBeaconChain} from "../../../chain/index.js";
import {IBeaconDb} from "../../../db/index.js";
import {ExecutionPayloadEnvelopesByRootRequest} from "../../../util/types.js";

export async function* onExecutionPayloadEnvelopesByRoot(
requestBody: ExecutionPayloadEnvelopesByRootRequest,
chain: IBeaconChain,
db: IBeaconDb
): AsyncIterable<ResponseOutgoing> {
// Spec: [max(GLOAS_FORK_EPOCH, current_epoch - MIN_EPOCHS_FOR_BLOCK_REQUESTS), current_epoch]
const currentEpoch = chain.clock.currentEpoch;
const minimumRequestEpoch = Math.max(
currentEpoch - chain.config.MIN_EPOCHS_FOR_BLOCK_REQUESTS,
chain.config.GLOAS_FORK_EPOCH
);

for (const root of requestBody) {
const rootHex = toRootHex(root);
const block = chain.forkChoice.getBlockHexDefaultStatus(rootHex);
// If the block is not in fork choice, it may be finalized. Attempt to find its slot in block archive
const slot = block ? block.slot : await db.blockArchive.getSlotByRoot(root);

if (slot === null) {
continue;
}

const requestedEpoch = computeEpochAtSlot(slot);
if (requestedEpoch < minimumRequestEpoch) {
continue;
}

const envelopeBytes = await chain.getSerializedExecutionPayloadEnvelope(slot, rootHex);
Comment thread
ensi321 marked this conversation as resolved.
if (envelopeBytes) {
yield {
data: envelopeBytes,
boundary: chain.config.getForkBoundaryAtEpoch(requestedEpoch),
};
}
}
}
12 changes: 12 additions & 0 deletions packages/beacon-node/src/network/reqresp/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BeaconBlocksByRootRequestType,
BlobSidecarsByRootRequestType,
DataColumnSidecarsByRootRequestType,
ExecutionPayloadEnvelopesByRootRequestType,
} from "../../../util/types.js";
import {GetReqRespHandlerFn, ReqRespMethod} from "../types.js";
import {onBeaconBlocksByRange} from "./beaconBlocksByRange.js";
Expand All @@ -14,6 +15,8 @@ import {onBlobSidecarsByRange} from "./blobSidecarsByRange.js";
import {onBlobSidecarsByRoot} from "./blobSidecarsByRoot.js";
import {onDataColumnSidecarsByRange} from "./dataColumnSidecarsByRange.js";
import {onDataColumnSidecarsByRoot} from "./dataColumnSidecarsByRoot.js";
import {onExecutionPayloadEnvelopesByRange} from "./executionPayloadEnvelopesByRange.js";
import {onExecutionPayloadEnvelopesByRoot} from "./executionPayloadEnvelopesByRoot.js";
import {onLightClientBootstrap} from "./lightClientBootstrap.js";
import {onLightClientFinalityUpdate} from "./lightClientFinalityUpdate.js";
import {onLightClientOptimisticUpdate} from "./lightClientOptimisticUpdate.js";
Expand Down Expand Up @@ -62,6 +65,15 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh
return onDataColumnSidecarsByRoot(body, chain, db, peerId, peerClient);
},

[ReqRespMethod.ExecutionPayloadEnvelopesByRoot]: (req) => {
const body = ExecutionPayloadEnvelopesByRootRequestType(chain.config).deserialize(req.data);
return onExecutionPayloadEnvelopesByRoot(body, chain, db);
},
[ReqRespMethod.ExecutionPayloadEnvelopesByRange]: (req) => {
const body = ssz.gloas.ExecutionPayloadEnvelopesByRangeRequest.deserialize(req.data);
return onExecutionPayloadEnvelopesByRange(body, chain, db);
},

[ReqRespMethod.LightClientBootstrap]: (req) => {
const body = ssz.Root.deserialize(req.data);
return onLightClientBootstrap(body, chain);
Expand Down
12 changes: 12 additions & 0 deletions packages/beacon-node/src/network/reqresp/protocols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,18 @@ export const DataColumnSidecarsByRoot = toProtocol({
contextBytesType: ContextBytesType.ForkDigest,
});

export const ExecutionPayloadEnvelopesByRoot = toProtocol({
method: ReqRespMethod.ExecutionPayloadEnvelopesByRoot,
version: Version.V1,
contextBytesType: ContextBytesType.ForkDigest,
});

export const ExecutionPayloadEnvelopesByRange = toProtocol({
method: ReqRespMethod.ExecutionPayloadEnvelopesByRange,
version: Version.V1,
contextBytesType: ContextBytesType.ForkDigest,
});

export const LightClientBootstrap = toProtocol({
method: ReqRespMethod.LightClientBootstrap,
version: Version.V1,
Expand Down
18 changes: 18 additions & 0 deletions packages/beacon-node/src/network/reqresp/rateLimit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,24 @@ export const rateLimitQuotas: (fork: ForkName, config: BeaconConfig) => Record<R
req.reduce((total, item) => total + item.columns.length, 0)
),
},
[ReqRespMethod.ExecutionPayloadEnvelopesByRoot]: {
byPeer: {quota: config.MAX_REQUEST_PAYLOADS, quotaTimeMs: 10_000},
getRequestCount: getRequestCountFn(
fork,
config,
ReqRespMethod.ExecutionPayloadEnvelopesByRoot,
(req) => req.length
),
},
[ReqRespMethod.ExecutionPayloadEnvelopesByRange]: {
byPeer: {quota: config.MAX_REQUEST_BLOCKS_DENEB, quotaTimeMs: 10_000},
getRequestCount: getRequestCountFn(
fork,
config,
ReqRespMethod.ExecutionPayloadEnvelopesByRange,
(req) => req.count
),
},
[ReqRespMethod.LightClientBootstrap]: {
// As similar in the nature of `Status` protocol so we use the same rate limits.
byPeer: {quota: 5, quotaTimeMs: 15_000},
Expand Down
2 changes: 2 additions & 0 deletions packages/beacon-node/src/network/reqresp/score.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ export function onOutgoingReqRespError(e: RequestError, method: ReqRespMethod):
return PeerAction.LowToleranceError;
case ReqRespMethod.BeaconBlocksByRange:
case ReqRespMethod.BeaconBlocksByRoot:
case ReqRespMethod.ExecutionPayloadEnvelopesByRoot:
case ReqRespMethod.ExecutionPayloadEnvelopesByRange:
Comment thread
ensi321 marked this conversation as resolved.
Comment thread
ensi321 marked this conversation as resolved.
return PeerAction.MidToleranceError;
default:
return null;
Expand Down
Loading
Loading