Skip to content

Commit

Permalink
refactor the type reconstructions for builder
Browse files Browse the repository at this point in the history
  • Loading branch information
g11tech committed Oct 28, 2023
1 parent a5f1ef4 commit fa38aa2
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 139 deletions.
97 changes: 22 additions & 75 deletions packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import {fromHexString, toHexString} from "@chainsafe/ssz";
import {routes, ServerApi, ResponseFormat} from "@lodestar/api";
import {computeTimeAtSlot, signedBlindedBlockToFull, signedBlindedBlobSidecarsToFull} from "@lodestar/state-transition";
import {
computeTimeAtSlot,
parseSignedBlindedBlockOrContents,
reconstructFullBlockOrContents,
} from "@lodestar/state-transition";
import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
import {sleep, toHex, LogDataBasic} from "@lodestar/utils";
import {allForks, deneb, isSignedBlockContents, isSignedBlindedBlockContents} from "@lodestar/types";
import {sleep, toHex} from "@lodestar/utils";
import {allForks, deneb, isSignedBlockContents, ProducedBlockSource} from "@lodestar/types";
import {BlockSource, getBlockInput, ImportBlockOpts, BlockInput} from "../../../../chain/blocks/types.js";
import {promiseAllMaybeAsync} from "../../../../util/promises.js";
import {isOptimisticBlock} from "../../../../util/forkChoice.js";
Expand All @@ -15,11 +19,6 @@ import {resolveBlockId, toBeaconHeaderResponse} from "./utils.js";

type PublishBlockOpts = ImportBlockOpts & {broadcastValidation?: routes.beacon.BroadcastValidation};

type ParsedSignedBlindedBlockOrContents = {
signedBlindedBlock: allForks.SignedBlindedBeaconBlock;
signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars | null;
};

/**
* Validator clock may be advanced from beacon's clock. If the validator requests a resource in a
* future slot, wait some time instead of rejecting the request because it's in the future
Expand Down Expand Up @@ -152,27 +151,29 @@ export function getBeaconBlockApi({
.getBlindedForkTypes(signedBlindedBlock.message.slot)
.BeaconBlock.hashTreeRoot(signedBlindedBlock.message)
);
const logCtx = {blockRoot, slot};

// Either the payload/blobs are cached from i) engine locally or ii) they are from the builder
//
// executionPayload can be null or a real payload in locally produced, its only undefined when
// the block came from the builder
const executionPayload = chain.producedBlockRoot.get(blockRoot);
// executionPayload can be null or a real payload in locally produced so check for presence of root
const source = chain.producedBlockRoot.has(blockRoot) ? ProducedBlockSource.engine : ProducedBlockSource.builder;

const executionPayload = chain.producedBlockRoot.get(blockRoot) ?? null;
const blobSidecars = executionPayload
? chain.producedBlobSidecarsCache.get(toHex(executionPayload.blockHash))
: undefined;
const blobs = blobSidecars ? blobSidecars.map((blobSidecar) => blobSidecar.blob) : null;

const signedBlockOrContents =
executionPayload !== undefined
? reconstructLocalBlockOrContents(
chain,
{signedBlindedBlock, signedBlindedBlobSidecars},
executionPayload,
logCtx
)
: await reconstructBuilderBlockOrContents(chain, signedBlindedBlockOrContents, logCtx);
source === ProducedBlockSource.engine
? reconstructFullBlockOrContents({signedBlindedBlock, signedBlindedBlobSidecars}, {executionPayload, blobs})
: await reconstructBuilderBlockOrContents(chain, signedBlindedBlockOrContents);

// the full block is published by relay and it's possible that the block is already known to us
// by gossip
//
// see: https://github.com/ChainSafe/lodestar/issues/5404
const logCtx = {blockRoot, slot, source: executionPayload !== undefined ? "execution" : "builder"};
chain.logger.info("Publishing assembled block", logCtx);
return publishBlock(signedBlockOrContents, {...opts, ignoreIfKnown: true});
};

Expand Down Expand Up @@ -365,69 +366,15 @@ export function getBeaconBlockApi({
};
}

function parseSignedBlindedBlockOrContents(
signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents
): ParsedSignedBlindedBlockOrContents {
if (isSignedBlindedBlockContents(signedBlindedBlockOrContents)) {
const signedBlindedBlock = signedBlindedBlockOrContents.signedBlindedBlock;
const signedBlindedBlobSidecars = signedBlindedBlockOrContents.signedBlindedBlobSidecars;
return {signedBlindedBlock, signedBlindedBlobSidecars};
} else {
return {signedBlindedBlock: signedBlindedBlockOrContents, signedBlindedBlobSidecars: null};
}
}

function reconstructLocalBlockOrContents(
chain: ApiModules["chain"],
{signedBlindedBlock, signedBlindedBlobSidecars}: ParsedSignedBlindedBlockOrContents,
executionPayload: allForks.ExecutionPayload | null,
logCtx: Record<string, LogDataBasic>
): allForks.SignedBeaconBlockOrContents {
const signedBlock = signedBlindedBlockToFull(signedBlindedBlock, executionPayload);
if (executionPayload !== null) {
Object.assign(logCtx, {transactions: executionPayload.transactions.length});
}

if (signedBlindedBlobSidecars !== null) {
if (executionPayload === null) {
throw Error("Missing locally produced executionPayload for deneb+ publishBlindedBlock");
}

const blockHash = toHex(executionPayload.blockHash);
const blobSidecars = chain.producedBlobSidecarsCache.get(blockHash);
if (blobSidecars === undefined) {
throw Error("Missing blobSidecars from the local execution cache");
}
if (blobSidecars.length !== signedBlindedBlobSidecars.length) {
throw Error(
`Length mismatch signedBlindedBlobSidecars=${signedBlindedBlobSidecars.length} blobSidecars=${blobSidecars.length}`
);
}
const signedBlobSidecars = signedBlindedBlobSidecarsToFull(
signedBlindedBlobSidecars,
blobSidecars.map((blobSidecar) => blobSidecar.blob)
);

Object.assign(logCtx, {blobs: signedBlindedBlobSidecars.length});
chain.logger.verbose("Block & blobs assembled from locally cached payload", logCtx);
return {signedBlock, signedBlobSidecars} as allForks.SignedBeaconBlockOrContents;
} else {
chain.logger.verbose("Block assembled from locally cached payload", logCtx);
return signedBlock as allForks.SignedBeaconBlockOrContents;
}
}

async function reconstructBuilderBlockOrContents(
chain: ApiModules["chain"],
signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents,
logCtx: Record<string, LogDataBasic>
signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents
): Promise<allForks.SignedBeaconBlockOrContents> {
const executionBuilder = chain.executionBuilder;
if (!executionBuilder) {
throw Error("exeutionBuilder required to publish SignedBlindedBeaconBlock");
}

const signedBlockOrContents = await executionBuilder.submitBlindedBlock(signedBlindedBlockOrContents);
chain.logger.verbose("Publishing block assembled from the builder", logCtx);
return signedBlockOrContents;
}
73 changes: 10 additions & 63 deletions packages/beacon-node/src/execution/builder/http.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import {byteArrayEquals, toHexString} from "@chainsafe/ssz";
import {allForks, bellatrix, Slot, Root, BLSPubkey, ssz, deneb, Wei} from "@lodestar/types";
import {
allForks,
bellatrix,
Slot,
Root,
BLSPubkey,
ssz,
deneb,
Wei,
isExecutionPayloadAndBlobsBundle,
isSignedBlindedBlockContents,
} from "@lodestar/types";
parseSignedBlindedBlockOrContents,
parseExecutionPayloadAndBlobsBundle,
reconstructFullBlockOrContents,
} from "@lodestar/state-transition";
import {ChainForkConfig} from "@lodestar/config";
import {getClient, Api as BuilderApi} from "@lodestar/api/builder";
import {SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params";
Expand Down Expand Up @@ -125,26 +119,9 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
ApiError.assert(res, "execution.builder.submitBlindedBlock");
const {data} = res.response;

let executionPayload: allForks.ExecutionPayload;
let blobsBundle: deneb.BlobsBundle | null;

if (isExecutionPayloadAndBlobsBundle(data)) {
executionPayload = data.executionPayload;
blobsBundle = data.blobsBundle;
} else {
executionPayload = data;
blobsBundle = null;
}

let signedBlindedBlock: allForks.SignedBlindedBeaconBlock;
let signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars | null;
if (isSignedBlindedBlockContents(signedBlindedBlockOrContents)) {
signedBlindedBlock = signedBlindedBlockOrContents.signedBlindedBlock;
signedBlindedBlobSidecars = signedBlindedBlockOrContents.signedBlindedBlobSidecars;
} else {
signedBlindedBlock = signedBlindedBlockOrContents;
signedBlindedBlobSidecars = null;
}
const {executionPayload, blobsBundle} = parseExecutionPayloadAndBlobsBundle(data);
const {signedBlindedBlock, signedBlindedBlobSidecars} =
parseSignedBlindedBlockOrContents(signedBlindedBlockOrContents);

// some validations for execution payload
const expectedTransactionsRoot = signedBlindedBlock.message.body.executionPayloadHeader.transactionsRoot;
Expand All @@ -157,37 +134,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder {
);
}

const signedBlock: bellatrix.SignedBeaconBlock = {
...signedBlindedBlock,
message: {...signedBlindedBlock.message, body: {...signedBlindedBlock.message.body, executionPayload}},
};

if (signedBlindedBlobSidecars !== null) {
if (blobsBundle === null) {
throw Error("Invalid Builder response with missing blobsBundle for deneb+ forks");
}
if (signedBlindedBlobSidecars.length !== blobsBundle.blobs.length) {
throw Error(
`Invalid number of blobs returned by builder, expected=$${signedBlindedBlobSidecars.length} received=${blobsBundle.blobs.length}`
);
}
const signedBlobSidecars = signedBlindedBlobSidecars.map((_v, i) => {
// signedBlindedBlobSidecars and blobsBundle can't be null as we checked above but
// typescript can't seem to figure that out
if (signedBlindedBlobSidecars === null || blobsBundle === null) {
throw Error("Internal Error - signedBlindedBlobSidecars or blobsBundle is null");
}

const signedBlindedBlobSidecar = signedBlindedBlobSidecars[i];
const blob = blobsBundle.blobs[i];
return {signature: signedBlindedBlobSidecar.signature, message: {...signedBlindedBlobSidecar.message, blob}};
});
return {signedBlock, signedBlobSidecars};
} else {
if (blobsBundle !== null) {
throw Error("Invalid Builder response with blobsBundle for deneb- forks");
}
return signedBlock;
}
const blobs = blobsBundle ? blobsBundle.blobs : null;
return reconstructFullBlockOrContents({signedBlindedBlock, signedBlindedBlobSidecars}, {executionPayload, blobs});
}
}
69 changes: 68 additions & 1 deletion packages/state-transition/src/util/blindedBlock.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
import {ChainForkConfig} from "@lodestar/config";
import {ForkSeq} from "@lodestar/params";
import {allForks, phase0, Root, isBlindedBeaconBlock, isBlindedBlobSidecar, deneb, ssz} from "@lodestar/types";
import {
allForks,
phase0,
Root,
deneb,
ssz,
isBlindedBeaconBlock,
isBlindedBlobSidecar,
isSignedBlindedBlockContents,
isExecutionPayloadAndBlobsBundle,
} from "@lodestar/types";

import {executionPayloadToPayloadHeader} from "./execution.js";

type ParsedSignedBlindedBlockOrContents = {
signedBlindedBlock: allForks.SignedBlindedBeaconBlock;
signedBlindedBlobSidecars: deneb.SignedBlindedBlobSidecars | null;
};

export function blindedOrFullBlockHashTreeRoot(
config: ChainForkConfig,
blindedOrFull: allForks.FullOrBlindedBeaconBlock
Expand Down Expand Up @@ -99,3 +114,55 @@ export function signedBlindedBlobSidecarsToFull(
});
return signedBlobSidecars;
}

export function parseSignedBlindedBlockOrContents(
signedBlindedBlockOrContents: allForks.SignedBlindedBeaconBlockOrContents
): ParsedSignedBlindedBlockOrContents {
if (isSignedBlindedBlockContents(signedBlindedBlockOrContents)) {
const signedBlindedBlock = signedBlindedBlockOrContents.signedBlindedBlock;
const signedBlindedBlobSidecars = signedBlindedBlockOrContents.signedBlindedBlobSidecars;
return {signedBlindedBlock, signedBlindedBlobSidecars};
} else {
return {signedBlindedBlock: signedBlindedBlockOrContents, signedBlindedBlobSidecars: null};
}
}

export function parseExecutionPayloadAndBlobsBundle(
data: allForks.ExecutionPayload | allForks.ExecutionPayloadAndBlobsBundle
): {executionPayload: allForks.ExecutionPayload; blobsBundle: deneb.BlobsBundle | null} {
if (isExecutionPayloadAndBlobsBundle(data)) {
return data;
} else {
return {
executionPayload: data,
blobsBundle: null,
};
}
}

export function reconstructFullBlockOrContents(
{signedBlindedBlock, signedBlindedBlobSidecars}: ParsedSignedBlindedBlockOrContents,
{executionPayload, blobs}: {executionPayload: allForks.ExecutionPayload | null; blobs: deneb.Blobs | null}
): allForks.SignedBeaconBlockOrContents {
const signedBlock = signedBlindedBlockToFull(signedBlindedBlock, executionPayload);

if (signedBlindedBlobSidecars !== null) {
if (executionPayload === null) {
throw Error("Missing locally produced executionPayload for deneb+ publishBlindedBlock");
}

if (blobs === null) {
throw Error("Missing blobs from the local execution cache");
}
if (blobs.length !== signedBlindedBlobSidecars.length) {
throw Error(
`Length mismatch signedBlindedBlobSidecars=${signedBlindedBlobSidecars.length} blobs=${blobs.length}`
);
}
const signedBlobSidecars = signedBlindedBlobSidecarsToFull(signedBlindedBlobSidecars, blobs);

return {signedBlock, signedBlobSidecars} as allForks.SignedBeaconBlockOrContents;
} else {
return signedBlock as allForks.SignedBeaconBlockOrContents;
}
}

0 comments on commit fa38aa2

Please sign in to comment.