Skip to content

Commit

Permalink
feat: allow configuring broadcast validation on validator for proposa…
Browse files Browse the repository at this point in the history
…ls (#6151)

* feat: allow configuring broadcast validation on validator for proposals

* fix test

* add bodyroot log

* typo
  • Loading branch information
g11tech authored and philknows committed Dec 2, 2023
1 parent f0d3143 commit e12a21f
Show file tree
Hide file tree
Showing 7 changed files with 71 additions and 15 deletions.
14 changes: 12 additions & 2 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import path from "node:path";
import {CompositeTypeAny, fromHexString, TreeView, Type} from "@chainsafe/ssz";
import {CompositeTypeAny, fromHexString, toHexString, TreeView, Type} from "@chainsafe/ssz";
import {
BeaconStateAllForks,
CachedBeaconStateAllForks,
Expand Down Expand Up @@ -496,14 +496,24 @@ export class BeaconChain implements IBeaconChain {
proposerPubKey,
});

// The hashtree root computed here for debug log will get cached and hence won't introduce additional delays
const bodyRoot =
blockType === BlockType.Full
? this.config.getForkTypes(slot).BeaconBlockBody.hashTreeRoot(body)
: this.config.getBlindedForkTypes(slot).BeaconBlockBody.hashTreeRoot(body as allForks.BlindedBeaconBlockBody);
this.logger.debug("Computing block post state from the produced body", {
slot,
bodyRoot: toHexString(bodyRoot),
blockType,
});

const block = {
slot,
proposerIndex,
parentRoot: parentBlockRoot,
stateRoot: ZERO_HASH,
body,
} as AssembledBlockType<T>;

block.stateRoot = computeNewStateRoot(this.metrics, state, block);
const blockRoot =
blockType === BlockType.Full
Expand Down
16 changes: 16 additions & 0 deletions packages/cli/src/cmds/validator/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ export async function validatorHandler(args: IValidatorCliArgs & GlobalArgs): Pr
valProposerConfig,
distributed: args.distributed,
useProduceBlockV3: args.useProduceBlockV3,
broadcastValidation: parseBroadcastValidation(args.broadcastValidation),
},
metrics
);
Expand Down Expand Up @@ -268,3 +269,18 @@ function parseBuilderSelection(builderSelection?: string): routes.validator.Buil
}
return builderSelection as routes.validator.BuilderSelection;
}

function parseBroadcastValidation(broadcastValidation?: string): routes.beacon.BroadcastValidation | undefined {
if (broadcastValidation) {
switch (broadcastValidation) {
case "gossip":
case "consensus":
case "consensus_and_equivocation":
break;
default:
throw Error("Invalid input for broadcastValidation, check help");
}
}

return broadcastValidation as routes.beacon.BroadcastValidation;
}
7 changes: 7 additions & 0 deletions packages/cli/src/cmds/validator/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type IValidatorCliArgs = AccountValidatorArgs &
"builder.selection"?: string;

useProduceBlockV3?: boolean;
broadcastValidation?: string;

importKeystores?: string[];
importKeystoresPassword?: string;
Expand Down Expand Up @@ -250,6 +251,12 @@ export const validatorOptions: CliCommandOptions<IValidatorCliArgs> = {
defaultDescription: `${defaultOptions.useProduceBlockV3}`,
},

broadcastValidation: {
type: "string",
description: "Validations to be run by beacon node for the signed block prior to publishing",
defaultDescription: `${defaultOptions.broadcastValidation}`,
},

importKeystores: {
alias: ["keystore"], // Backwards compatibility with old `validator import` cmdx
description: "Path(s) to a directory or single file path to validator keystores, i.e. Launchpad validators",
Expand Down
29 changes: 19 additions & 10 deletions packages/validator/src/services/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export class BlockProposingService {
private readonly clock: IClock,
private readonly validatorStore: ValidatorStore,
private readonly metrics: Metrics | null,
private readonly opts: {useProduceBlockV3: boolean}
private readonly opts: {useProduceBlockV3: boolean; broadcastValidation: routes.beacon.BroadcastValidation}
) {
this.dutiesService = new BlockDutiesService(
config,
Expand Down Expand Up @@ -158,7 +158,9 @@ export class BlockProposingService {
signedBlobs = undefined;
}

await this.publishBlockWrapper(signedBlock, signedBlobs).catch((e: Error) => {
await this.publishBlockWrapper(signedBlock, signedBlobs, {
broadcastValidation: this.opts.broadcastValidation,
}).catch((e: Error) => {
this.metrics?.blockProposingErrors.inc({error: "publish"});
throw extendError(e, "Failed to publish block");
});
Expand All @@ -172,22 +174,29 @@ export class BlockProposingService {

private publishBlockWrapper = async (
signedBlock: allForks.FullOrBlindedSignedBeaconBlock,
signedBlobSidecars?: allForks.FullOrBlindedSignedBlobSidecar[]
signedBlobSidecars?: allForks.FullOrBlindedSignedBlobSidecar[],
opts: {broadcastValidation?: routes.beacon.BroadcastValidation} = {}
): Promise<void> => {
if (signedBlobSidecars === undefined) {
ApiError.assert(
isBlindedBeaconBlock(signedBlock.message)
? await this.api.beacon.publishBlindedBlock(signedBlock as allForks.SignedBlindedBeaconBlock)
: await this.api.beacon.publishBlockV2(signedBlock as allForks.SignedBeaconBlock)
? await this.api.beacon.publishBlindedBlockV2(signedBlock as allForks.SignedBlindedBeaconBlock, opts)
: await this.api.beacon.publishBlockV2(signedBlock as allForks.SignedBeaconBlock, opts)
);
} else {
ApiError.assert(
isBlindedBeaconBlock(signedBlock.message)
? await this.api.beacon.publishBlindedBlock({
signedBlindedBlock: signedBlock,
signedBlindedBlobSidecars: signedBlobSidecars,
} as allForks.SignedBlindedBlockContents)
: await this.api.beacon.publishBlockV2({signedBlock, signedBlobSidecars} as allForks.SignedBlockContents)
? await this.api.beacon.publishBlindedBlockV2(
{
signedBlindedBlock: signedBlock,
signedBlindedBlobSidecars: signedBlobSidecars,
} as allForks.SignedBlindedBlockContents,
opts
)
: await this.api.beacon.publishBlockV2(
{signedBlock, signedBlobSidecars} as allForks.SignedBlockContents,
opts
)
);
}
};
Expand Down
2 changes: 2 additions & 0 deletions packages/validator/src/services/validatorStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,8 @@ export const defaultOptions = {
builderAliasSelection: routes.validator.BuilderSelection.MaxProfit,
// turn it off by default, turn it back on once other clients support v3 api
useProduceBlockV3: false,
// spec asks for gossip validation by default
broadcastValidation: routes.beacon.BroadcastValidation.gossip,
};

/**
Expand Down
10 changes: 9 additions & 1 deletion packages/validator/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export type ValidatorOptions = {
valProposerConfig?: ValidatorProposerConfig;
distributed?: boolean;
useProduceBlockV3?: boolean;
broadcastValidation?: routes.beacon.BroadcastValidation;
};

// TODO: Extend the timeout, and let it be customizable
Expand Down Expand Up @@ -208,6 +209,7 @@ export class Validator {

const blockProposingService = new BlockProposingService(config, loggerVc, api, clock, validatorStore, metrics, {
useProduceBlockV3: opts.useProduceBlockV3 ?? defaultOptions.useProduceBlockV3,
broadcastValidation: opts.broadcastValidation ?? defaultOptions.broadcastValidation,
});

const attestationService = new AttestationService(
Expand Down Expand Up @@ -284,13 +286,19 @@ export class Validator {
await assertEqualGenesis(opts, genesis);
logger.info("Verified connected beacon node and validator have the same genesisValidatorRoot");

const {useProduceBlockV3, valProposerConfig} = opts;
const {
useProduceBlockV3 = defaultOptions.useProduceBlockV3,
broadcastValidation = defaultOptions.broadcastValidation,
valProposerConfig,
} = opts;
const defaultBuilderSelection =
valProposerConfig?.defaultConfig.builder?.selection ?? defaultOptions.builderSelection;
const strictFeeRecipientCheck = valProposerConfig?.defaultConfig.strictFeeRecipientCheck ?? false;
const suggestedFeeRecipient = valProposerConfig?.defaultConfig.feeRecipient ?? defaultOptions.suggestedFeeRecipient;

logger.info("Initializing validator", {
useProduceBlockV3,
broadcastValidation,
defaultBuilderSelection,
suggestedFeeRecipient,
strictFeeRecipientCheck,
Expand Down
8 changes: 6 additions & 2 deletions packages/validator/test/unit/services/block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {createChainForkConfig} from "@lodestar/config";
import {config as mainnetConfig} from "@lodestar/config/default";
import {sleep} from "@lodestar/utils";
import {ssz} from "@lodestar/types";
import {HttpStatusCode} from "@lodestar/api";
import {HttpStatusCode, routes} from "@lodestar/api";
import {ForkName} from "@lodestar/params";
import {BlockProposingService} from "../../../src/services/block.js";
import {ValidatorStore} from "../../../src/services/validatorStore.js";
Expand Down Expand Up @@ -52,6 +52,7 @@ describe("BlockDutiesService", function () {
// use produceBlockV3
const blockService = new BlockProposingService(config, loggerVc, api, clock, validatorStore, null, {
useProduceBlockV3: true,
broadcastValidation: routes.beacon.BroadcastValidation.consensus,
});

const signedBlock = ssz.phase0.SignedBeaconBlock.defaultValue();
Expand All @@ -78,6 +79,9 @@ describe("BlockDutiesService", function () {

// Must have submitted the block received on signBlock()
expect(api.beacon.publishBlockV2.callCount).to.equal(1, "publishBlock() must be called once");
expect(api.beacon.publishBlockV2.getCall(0).args).to.deep.equal([signedBlock], "wrong publishBlock() args");
expect(api.beacon.publishBlockV2.getCall(0).args).to.deep.equal(
[signedBlock, {broadcastValidation: routes.beacon.BroadcastValidation.consensus}],
"wrong publishBlock() args"
);
});
});

0 comments on commit e12a21f

Please sign in to comment.