Skip to content

Commit

Permalink
feat: switch blinded and full block production to produce block v3
Browse files Browse the repository at this point in the history
update the impl interfaces

Restore the previous versions

update test

fix oapi spec

fix tests

fix merge tests

fix tests

fix the tests

implement produce blockv3

refac forktypes

plug v3 into vlidator and get it working

mock test produceblockv3 and fix issues

fix lint

fixes

update head opts

cleanup the validator flags

shift fee recipient to extra args as well

typo

fix tests

missed commit

deprecate flag instead of removing

backward produceblock v2 compatability

lint

disable produce blockv3 for mixed tests

run on only execution

sim option fixes

improve log

pass args

fix spell check

sort wordlist

chore: sort wordlist

chore: fix spell check
  • Loading branch information
g11tech committed Oct 3, 2023
1 parent 6629242 commit e9c6aff
Show file tree
Hide file tree
Showing 39 changed files with 935 additions and 597 deletions.
1 change: 1 addition & 0 deletions .wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ nodemodule
overriden
params
plaintext
produceBlockV
prover
req
reqresp
Expand Down
2 changes: 1 addition & 1 deletion docs/usage/beacon-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ A young testnet should take a few hours to sync. If you see multiple or consiste

### Checkpoint Sync

If you are starting your node from a blank db, like starting from genesis, or from the last saved state in db and the network is now far ahead, your node will be susceptible to "long range attacks." Ethereum's solution to this is via something called weak subjectivity. [Read Vitalik's illuminating post explaining weak subjectivity.](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/).
If you are starting your node from a blank db, like starting from genesis, or from the last saved state in db and the network is now far ahead, your node will be susceptible to "long range attacks." Ethereum's solution to this is via something called weak subjectivity. [Read Vitalik's illuminating post explaining weak subjectivity.](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/).

If you have a synced beacon node available (e.g., your friend's node or an infrastructure provider) and a trusted checkpoint you can rely on, you can start off your beacon node in under a minute! And at the same time kicking the "long range attack" in its butt!

Expand Down
176 changes: 141 additions & 35 deletions packages/api/src/beacon/routes/validator.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ContainerType, fromHexString, toHexString, Type} from "@chainsafe/ssz";
import {ForkName, isForkBlobs, isForkExecution} from "@lodestar/params";
import {ForkName, ForkBlobs, isForkBlobs, isForkExecution, ForkPreBlobs} from "@lodestar/params";
import {
allForks,
altair,
Expand Down Expand Up @@ -27,12 +27,13 @@ import {
ArrayOf,
Schema,
WithVersion,
WithBlockValue,
WithExecutionPayloadValue,
reqOnlyBody,
ReqSerializers,
jsonType,
ContainerDataExecutionOptimistic,
ContainerData,
TypeJson,
} from "../../utils/index.js";
import {fromU64Str, fromGraffitiHex, toU64Str, U64Str, toGraffitiHex} from "../../utils/serdes.js";
import {
Expand All @@ -43,6 +44,34 @@ import {
} from "../../utils/routes.js";
import {ExecutionOptimistic} from "./beacon/block.js";

export enum BuilderSelection {
BuilderAlways = "builderalways",
MaxProfit = "maxprofit",
/** Only activate builder flow for DVT block proposal protocols */
BuilderOnly = "builderonly",
/** Only builds execution block*/
ExecutionOnly = "executiononly",
}

export type ExtraProduceBlockOps = {
feeRecipient?: string;
builderSelection?: BuilderSelection;
strictFeeRecipientCheck?: boolean;
};

export type ProduceBlockOrContentsRes = {executionPayloadValue: Wei} & (
| {data: allForks.BeaconBlock; version: ForkPreBlobs}
| {data: BlockContents; version: ForkBlobs}
);
export type ProduceBlindedBlockOrContentsRes = {executionPayloadValue: Wei} & (
| {data: allForks.BlindedBeaconBlock; version: ForkPreBlobs}
| {data: BlindedBlockContents; version: ForkBlobs}
);

export type ProduceFullOrBlindedBlockOrContentsRes =
| (ProduceBlockOrContentsRes & {executionPayloadBlinded: false})
| (ProduceBlindedBlockOrContentsRes & {executionPayloadBlinded: true});

// See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes

export type BeaconCommitteeSubscription = {
Expand Down Expand Up @@ -201,11 +230,10 @@ export type Api = {
produceBlock(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
feeRecipient?: string
graffiti: string
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock; blockValue: Wei}},
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock}},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;
Expand All @@ -221,30 +249,49 @@ export type Api = {
* @throws ApiError
*/
produceBlockV2(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: ProduceBlockOrContentsRes},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;

/**
* Requests a beacon node to produce a valid block, which can then be signed by a validator.
* Metadata in the response indicates the type of block produced, and the supported types of block
* will be added to as forks progress.
* @param slot The slot for which the block should be proposed.
* @param randaoReveal The validator's randao reveal value.
* @param graffiti Arbitrary data validator wants to include in block.
* @returns any Success response
* @throws ApiError
*/
produceBlockV3(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
feeRecipient?: string
skipRandaoVerification?: boolean,
opts?: ExtraProduceBlockOps
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: allForks.BeaconBlock | BlockContents; version: ForkName; blockValue: Wei}},
{
[HttpStatusCode.OK]: ProduceFullOrBlindedBlockOrContentsRes;
},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
>;

produceBlindedBlock(
slot: Slot,
randaoReveal: BLSSignature,
graffiti: string,
feeRecipient?: string
graffiti: string
): Promise<
ApiClientResponse<
{
[HttpStatusCode.OK]: {
data: allForks.BlindedBeaconBlock | BlindedBlockContents;
version: ForkName;
blockValue: Wei;
};
[HttpStatusCode.OK]: ProduceBlindedBlockOrContentsRes;
},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.SERVICE_UNAVAILABLE
>
Expand Down Expand Up @@ -410,6 +457,7 @@ export const routesData: RoutesData<Api> = {
getSyncCommitteeDuties: {url: "/eth/v1/validator/duties/sync/{epoch}", method: "POST"},
produceBlock: {url: "/eth/v1/validator/blocks/{slot}", method: "GET"},
produceBlockV2: {url: "/eth/v2/validator/blocks/{slot}", method: "GET"},
produceBlockV3: {url: "/eth/v3/validator/blocks/{slot}", method: "GET"},
produceBlindedBlock: {url: "/eth/v1/validator/blinded_blocks/{slot}", method: "GET"},
produceAttestationData: {url: "/eth/v1/validator/attestation_data", method: "GET"},
produceSyncCommitteeContribution: {url: "/eth/v1/validator/sync_committee_contribution", method: "GET"},
Expand All @@ -432,6 +480,17 @@ export type ReqTypes = {
getSyncCommitteeDuties: {params: {epoch: Epoch}; body: U64Str[]};
produceBlock: {params: {slot: number}; query: {randao_reveal: string; graffiti: string}};
produceBlockV2: {params: {slot: number}; query: {randao_reveal: string; graffiti: string; fee_recipient?: string}};
produceBlockV3: {
params: {slot: number};
query: {
randao_reveal: string;
graffiti: string;
skip_randao_verification?: boolean;
fee_recipient?: string;
builder_selection?: string;
strict_fee_recipient_check?: boolean;
};
};
produceBlindedBlock: {params: {slot: number}; query: {randao_reveal: string; graffiti: string}};
produceAttestationData: {query: {slot: number; committee_index: number}};
produceSyncCommitteeContribution: {query: {slot: number; subcommittee_index: number; beacon_block_root: string}};
Expand Down Expand Up @@ -487,20 +546,39 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
{jsonCase: "eth2"}
);

const produceBlock: ReqSerializers<Api, ReqTypes>["produceBlockV2"] = {
writeReq: (slot, randaoReveal, graffiti, feeRecipient) => ({
const produceBlockV3: ReqSerializers<Api, ReqTypes>["produceBlockV3"] = {
writeReq: (slot, randaoReveal, graffiti, skipRandaoVerification, opts) => ({
params: {slot},
query: {randao_reveal: toHexString(randaoReveal), graffiti: toGraffitiHex(graffiti), fee_recipient: feeRecipient},
query: {
randao_reveal: toHexString(randaoReveal),
graffiti: toGraffitiHex(graffiti),
fee_recipient: opts?.feeRecipient,
skip_randao_verification: skipRandaoVerification,
builder_selection: opts?.builderSelection,
strict_fee_recipient_check: opts?.strictFeeRecipientCheck,
},
}),
parseReq: ({params, query}) => [
params.slot,
fromHexString(query.randao_reveal),
fromGraffitiHex(query.graffiti),
query.fee_recipient,
query.skip_randao_verification,
{
feeRecipient: query.fee_recipient,
builderSelection: query.builder_selection as BuilderSelection,
strictFeeRecipientCheck: query.strict_fee_recipient_check,
},
],
schema: {
params: {slot: Schema.UintRequired},
query: {randao_reveal: Schema.StringRequired, graffiti: Schema.String, fee_recipient: Schema.String},
query: {
randao_reveal: Schema.StringRequired,
graffiti: Schema.String,
fee_recipient: Schema.String,
skip_randao_verification: Schema.Boolean,
builder_selection: Schema.String,
strict_fee_recipient_check: Schema.Boolean,
},
},
};

Expand Down Expand Up @@ -531,9 +609,10 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
},
},

produceBlock: produceBlock,
produceBlockV2: produceBlock,
produceBlindedBlock: produceBlock,
produceBlock: produceBlockV3 as ReqSerializers<Api, ReqTypes>["produceBlock"],
produceBlockV2: produceBlockV3 as ReqSerializers<Api, ReqTypes>["produceBlockV2"],
produceBlockV3,
produceBlindedBlock: produceBlockV3 as ReqSerializers<Api, ReqTypes>["produceBlindedBlock"],

produceAttestationData: {
writeReq: (index, slot) => ({query: {slot, committee_index: index}}),
Expand Down Expand Up @@ -641,23 +720,50 @@ export function getReturnTypes(): ReturnTypes<Api> {
{jsonCase: "eth2"}
);

const produceBlockOrContents = WithExecutionPayloadValue(
WithVersion<allForks.BeaconBlock | BlockContents>((fork: ForkName) =>
isForkBlobs(fork) ? AllForksBlockContentsResSerializer(() => fork) : ssz[fork].BeaconBlock
)
) as TypeJson<ProduceBlockOrContentsRes>;
const produceBlindedBlockOrContents = WithExecutionPayloadValue(
WithVersion<allForks.BlindedBeaconBlock | BlindedBlockContents>((fork: ForkName) =>
isForkBlobs(fork)
? AllForksBlindedBlockContentsResSerializer(() => fork)
: ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock
)
) as TypeJson<ProduceBlindedBlockOrContentsRes>;

return {
getAttesterDuties: WithDependentRootExecutionOptimistic(ArrayOf(AttesterDuty)),
getProposerDuties: WithDependentRootExecutionOptimistic(ArrayOf(ProposerDuty)),
getSyncCommitteeDuties: ContainerDataExecutionOptimistic(ArrayOf(SyncDuty)),
produceBlock: WithBlockValue(ContainerData(ssz.phase0.BeaconBlock)),
produceBlockV2: WithBlockValue(
WithVersion<allForks.BeaconBlock | BlockContents>((fork: ForkName) =>
isForkBlobs(fork) ? AllForksBlockContentsResSerializer(() => fork) : ssz[fork].BeaconBlock
)
),
produceBlindedBlock: WithBlockValue(
WithVersion<allForks.BlindedBeaconBlock | BlindedBlockContents>((fork: ForkName) =>
isForkBlobs(fork)
? AllForksBlindedBlockContentsResSerializer(() => fork)
: ssz.allForksBlinded[isForkExecution(fork) ? fork : ForkName.bellatrix].BeaconBlock
)
),

produceBlock: ContainerData(ssz.phase0.BeaconBlock),
produceBlockV2: produceBlockOrContents,
produceBlockV3: {
toJson: (data) => {
if (data.executionPayloadBlinded) {
return {
execution_payload_blinded: true,
...(produceBlindedBlockOrContents.toJson(data) as Record<string, unknown>),
};
} else {
return {
execution_payload_blinded: false,
...(produceBlockOrContents.toJson(data) as Record<string, unknown>),
};
}
},
fromJson: (data) => {
if ((data as {execution_payload_blinded: true}).execution_payload_blinded) {
return {executionPayloadBlinded: true, ...produceBlindedBlockOrContents.fromJson(data)};
} else {
return {executionPayloadBlinded: false, ...produceBlockOrContents.fromJson(data)};
}
},
},
produceBlindedBlock: produceBlindedBlockOrContents,

produceAttestationData: ContainerData(ssz.phase0.AttestationData),
produceSyncCommitteeContribution: ContainerData(ssz.altair.SyncCommitteeContribution),
getAggregatedAttestation: ContainerData(ssz.phase0.Attestation),
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/utils/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export enum Schema {
Object,
ObjectArray,
AnyArray,
Boolean,
}

/**
Expand Down Expand Up @@ -68,6 +69,9 @@ function getJsonSchemaItem(schema: Schema): JsonSchema {

case Schema.AnyArray:
return {type: "array"};

case Schema.Boolean:
return {type: "boolean"};
}
}

Expand Down
18 changes: 10 additions & 8 deletions packages/api/src/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export type RouteDef = {

export type ReqGeneric = {
params?: Record<string, string | number>;
query?: Record<string, string | number | (string | number)[]>;
query?: Record<string, string | number | boolean | (string | number)[]>;
body?: any;
headers?: Record<string, string[] | string | undefined>;
};
Expand Down Expand Up @@ -179,18 +179,20 @@ export function WithExecutionOptimistic<T extends {data: unknown}>(
}

/**
* SSZ factory helper to wrap an existing type with `{blockValue: Wei}`
* SSZ factory helper to wrap an existing type with `{executionPayloadValue: Wei}`
*/
export function WithBlockValue<T extends {data: unknown}>(type: TypeJson<T>): TypeJson<T & {blockValue: bigint}> {
export function WithExecutionPayloadValue<T extends {data: unknown}>(
type: TypeJson<T>
): TypeJson<T & {executionPayloadValue: bigint}> {
return {
toJson: ({blockValue, ...data}) => ({
toJson: ({executionPayloadValue, ...data}) => ({
...(type.toJson(data as unknown as T) as Record<string, unknown>),
block_value: blockValue.toString(),
execution_payload_value: executionPayloadValue.toString(),
}),
fromJson: ({block_value, ...data}: T & {block_value: string}) => ({
fromJson: ({execution_payload_value, ...data}: T & {execution_payload_value: string}) => ({
...type.fromJson(data),
// For cross client usage where beacon or validator are of separate clients, blockValue could be missing
blockValue: BigInt(block_value ?? "0"),
// For cross client usage where beacon or validator are of separate clients, executionPayloadValue could be missing
executionPayloadValue: BigInt(execution_payload_value ?? "0"),
}),
};
}
Expand Down
31 changes: 25 additions & 6 deletions packages/api/test/unit/beacon/testData/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,38 @@ export const testData: GenericServerTestCases<Api> = {
},
},
produceBlock: {
args: [32000, randaoReveal, graffiti, feeRecipient],
res: {data: ssz.phase0.BeaconBlock.defaultValue(), blockValue: ssz.Wei.defaultValue()},
args: [32000, randaoReveal, graffiti],
res: {data: ssz.phase0.BeaconBlock.defaultValue()},
},
produceBlockV2: {
args: [32000, randaoReveal, graffiti, feeRecipient],
res: {data: ssz.altair.BeaconBlock.defaultValue(), version: ForkName.altair, blockValue: ssz.Wei.defaultValue()},
args: [32000, randaoReveal, graffiti],
res: {
data: ssz.altair.BeaconBlock.defaultValue(),
version: ForkName.altair,
executionPayloadValue: ssz.Wei.defaultValue(),
},
},
produceBlockV3: {
args: [
32000,
randaoReveal,
graffiti,
true,
{feeRecipient, builderSelection: undefined, strictFeeRecipientCheck: undefined},
],
res: {
data: ssz.altair.BeaconBlock.defaultValue(),
version: ForkName.altair,
executionPayloadValue: ssz.Wei.defaultValue(),
executionPayloadBlinded: false,
},
},
produceBlindedBlock: {
args: [32000, randaoReveal, graffiti, feeRecipient],
args: [32000, randaoReveal, graffiti],
res: {
data: ssz.bellatrix.BlindedBeaconBlock.defaultValue(),
version: ForkName.bellatrix,
blockValue: ssz.Wei.defaultValue(),
executionPayloadValue: ssz.Wei.defaultValue(),
},
},
produceAttestationData: {
Expand Down
Loading

0 comments on commit e9c6aff

Please sign in to comment.