Skip to content

Commit

Permalink
Add beacon_block_and_blobs_sidecar gossip type
Browse files Browse the repository at this point in the history
  • Loading branch information
dgcoffman committed Nov 7, 2022
1 parent f5c8cfa commit e6c13e6
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 91 deletions.
2 changes: 2 additions & 0 deletions packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,8 @@ export function getBeaconBlockApi({

metrics?.registerBeaconBlock(OpSource.api, seenTimestampSec, signedBlock.message);

console.log("Publishing block to P2P (gossip) network", signedBlock);

await Promise.all([
// Send the block, regardless of whether or not it is valid. The API
// specification is very clear that this is the desired behaviour.
Expand Down
3 changes: 3 additions & 0 deletions packages/beacon-node/src/network/gossip/gossipsub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ export class Eth2Gossipsub extends GossipSub {

async publishBeaconBlock(signedBlock: allForks.SignedBeaconBlock): Promise<void> {
const fork = this.config.getForkName(signedBlock.message.slot);
console.log(`Publishing beacon block for fork: ${fork}`);

// TODO: For EIP-4844, switch this to GossipType.beacon_block_and_blobs_sidecar
await this.publishObject<GossipType.beacon_block>({type: GossipType.beacon_block, fork}, signedBlock);
}

Expand Down
132 changes: 75 additions & 57 deletions packages/beacon-node/src/network/gossip/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {toHexString} from "@chainsafe/ssz";
import {IBeaconConfig} from "@lodestar/config";
import {phase0, ssz} from "@lodestar/types";
import {ILogger, prettyBytes} from "@lodestar/utils";
import {SignedBeaconBlock} from "@lodestar/types/lib/phase0/types.js";
import {ForkName} from "@lodestar/params";
import {IMetrics} from "../../../metrics/index.js";
import {OpSource} from "../../../metrics/validatorMonitor.js";
import {IBeaconChain} from "../../../chain/index.js";
Expand Down Expand Up @@ -74,69 +76,85 @@ const MAX_UNKNOWN_BLOCK_ROOT_RETRIES = 1;
export function getGossipHandlers(modules: ValidatorFnsModules, options: GossipHandlerOpts): GossipHandlers {
const {chain, config, metrics, network, logger} = modules;

return {
[GossipType.beacon_block]: async (signedBlock, topic, peerIdStr, seenTimestampSec) => {
const slot = signedBlock.message.slot;
const forkTypes = config.getForkTypes(slot);
const blockHex = prettyBytes(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
logger.verbose("Received gossip block", {
slot: slot,
root: blockHex,
curentSlot: chain.clock.currentSlot,
peerId: peerIdStr,
delaySec,
});

try {
await validateGossipBlock(config, chain, signedBlock, topic.fork);
} catch (e) {
if (e instanceof BlockGossipError) {
if (e instanceof BlockGossipError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) {
logger.debug("Gossip block has error", {slot, root: blockHex, code: e.type.code});
network.events.emit(NetworkEvent.unknownBlockParent, signedBlock, peerIdStr);
}
}
async function handleBeaconBlock(
signedBlock: SignedBeaconBlock,
fork: ForkName,
peerIdStr: string,
seenTimestampSec: number
): Promise<void> {
const slot = signedBlock.message.slot;
const forkTypes = config.getForkTypes(slot);
const blockHex = prettyBytes(forkTypes.BeaconBlock.hashTreeRoot(signedBlock.message));
const delaySec = chain.clock.secFromSlot(slot, seenTimestampSec);
logger.verbose("Received gossip block", {
slot: slot,
root: blockHex,
curentSlot: chain.clock.currentSlot,
peerId: peerIdStr,
delaySec,
});

if (e instanceof BlockGossipError && e.action === GossipAction.REJECT) {
chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`);
try {
await validateGossipBlock(config, chain, signedBlock, fork);
} catch (e) {
if (e instanceof BlockGossipError) {
if (e instanceof BlockGossipError && e.type.code === BlockErrorCode.PARENT_UNKNOWN) {
logger.debug("Gossip block has error", {slot, root: blockHex, code: e.type.code});
network.events.emit(NetworkEvent.unknownBlockParent, signedBlock, peerIdStr);
}
}

throw e;
if (e instanceof BlockGossipError && e.action === GossipAction.REJECT) {
chain.persistInvalidSszValue(forkTypes.SignedBeaconBlock, signedBlock, `gossip_reject_slot_${slot}`);
}

// Handler - MUST NOT `await`, to allow validation result to be propagated

metrics?.registerBeaconBlock(OpSource.gossip, seenTimestampSec, signedBlock.message);

// `validProposerSignature = true`, in gossip validation the proposer signature is checked
// At gossip time, it's critical to keep a good number of mesh peers.
// To do that, the Gossip Job Wait Time should be consistently <3s to avoid the behavior penalties in gossip
// Gossip Job Wait Time depends on the BLS Job Wait Time
// so `blsVerifyOnMainThread = true`: we want to verify signatures immediately without affecting the bls thread pool.
// otherwise we can't utilize bls thread pool capacity and Gossip Job Wait Time can't be kept low consistently.
// See https://github.com/ChainSafe/lodestar/issues/3792
chain
.processBlock(signedBlock, {validProposerSignature: true, blsVerifyOnMainThread: true})
.then(() => {
// Returns the delay between the start of `block.slot` and `current time`
const delaySec = chain.clock.secFromSlot(slot);
metrics?.gossipBlock.elapsedTimeTillProcessed.observe(delaySec);
})
.catch((e) => {
if (e instanceof BlockError) {
switch (e.type.code) {
case BlockErrorCode.ALREADY_KNOWN:
case BlockErrorCode.PARENT_UNKNOWN:
case BlockErrorCode.PRESTATE_MISSING:
case BlockErrorCode.EXECUTION_ENGINE_ERROR:
break;
default:
network.reportPeer(peerIdFromString(peerIdStr), PeerAction.LowToleranceError, "BadGossipBlock");
}
throw e;
}

// Handler - MUST NOT `await`, to allow validation result to be propagated

metrics?.registerBeaconBlock(OpSource.gossip, seenTimestampSec, signedBlock.message);

// `validProposerSignature = true`, in gossip validation the proposer signature is checked
// At gossip time, it's critical to keep a good number of mesh peers.
// To do that, the Gossip Job Wait Time should be consistently <3s to avoid the behavior penalties in gossip
// Gossip Job Wait Time depends on the BLS Job Wait Time
// so `blsVerifyOnMainThread = true`: we want to verify signatures immediately without affecting the bls thread pool.
// otherwise we can't utilize bls thread pool capacity and Gossip Job Wait Time can't be kept low consistently.
// See https://github.com/ChainSafe/lodestar/issues/3792
chain
.processBlock(signedBlock, {validProposerSignature: true, blsVerifyOnMainThread: true})
.then(() => {
// Returns the delay between the start of `block.slot` and `current time`
const delaySec = chain.clock.secFromSlot(slot);
metrics?.gossipBlock.elapsedTimeTillProcessed.observe(delaySec);
})
.catch((e) => {
if (e instanceof BlockError) {
switch (e.type.code) {
case BlockErrorCode.ALREADY_KNOWN:
case BlockErrorCode.PARENT_UNKNOWN:
case BlockErrorCode.PRESTATE_MISSING:
case BlockErrorCode.EXECUTION_ENGINE_ERROR:
break;
default:
network.reportPeer(peerIdFromString(peerIdStr), PeerAction.LowToleranceError, "BadGossipBlock");
}
logger.error("Error receiving block", {slot, peer: peerIdStr}, e as Error);
});
}
logger.error("Error receiving block", {slot, peer: peerIdStr}, e as Error);
});
}

return {
[GossipType.beacon_block_and_blobs_sidecar]: async (signedBlock, topic, peerIdStr, seenTimestampSec) => {
const {beaconBlock, blobsSidecar: _} = signedBlock;
// TODO EIP-4844: Validate blobs

return handleBeaconBlock(beaconBlock, topic.fork, peerIdStr, seenTimestampSec);
},

[GossipType.beacon_block]: async (signedBlock, topic, peerIdStr, seenTimestampSec) => {
return handleBeaconBlock(signedBlock, topic.fork, peerIdStr, seenTimestampSec);
},

[GossipType.beacon_aggregate_and_proof]: async (signedAggregateAndProof, _topic, _peer, seenTimestampSec) => {
Expand Down
6 changes: 5 additions & 1 deletion packages/beacon-node/src/network/gossip/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {Message} from "@libp2p/interface-pubsub";
import StrictEventEmitter from "strict-event-emitter-types";
import {MessageAcceptance, PeerIdStr} from "@chainsafe/libp2p-gossipsub/types";
import {ForkName} from "@lodestar/params";
import {allForks, altair, phase0} from "@lodestar/types";
import {allForks, altair, eip4844, phase0} from "@lodestar/types";
import {IBeaconConfig} from "@lodestar/config";
import {ILogger} from "@lodestar/utils";
import {IBeaconChain} from "../../chain/index.js";
Expand All @@ -24,6 +24,8 @@ export enum GossipType {
sync_committee = "sync_committee",
light_client_finality_update = "light_client_finality_update",
light_client_optimistic_update = "light_client_optimistic_update",
// eip4844
beacon_block_and_blobs_sidecar = "beacon_block_and_blobs_sidecar",
}

export enum GossipEncoding {
Expand Down Expand Up @@ -52,6 +54,7 @@ export type GossipTopicTypeMap = {
[GossipType.sync_committee]: {type: GossipType.sync_committee; subnet: number};
[GossipType.light_client_finality_update]: {type: GossipType.light_client_finality_update};
[GossipType.light_client_optimistic_update]: {type: GossipType.light_client_optimistic_update};
[GossipType.beacon_block_and_blobs_sidecar]: {type: GossipType.beacon_block_and_blobs_sidecar};
};

export type GossipTopicMap = {
Expand All @@ -74,6 +77,7 @@ export type GossipTypeMap = {
[GossipType.sync_committee]: altair.SyncCommitteeMessage;
[GossipType.light_client_finality_update]: altair.LightClientFinalityUpdate;
[GossipType.light_client_optimistic_update]: altair.LightClientOptimisticUpdate;
[GossipType.beacon_block_and_blobs_sidecar]: eip4844.SignedBeaconBlockAndBlobsSidecar;
};

export type GossipFnByType = {
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/network/gossip/topic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function stringifyGossipTopicType(topic: GossipTopic): string {
case GossipType.sync_committee_contribution_and_proof:
case GossipType.light_client_finality_update:
case GossipType.light_client_optimistic_update:
case GossipType.beacon_block_and_blobs_sidecar:
return topic.type;
case GossipType.beacon_attestation:
case GossipType.sync_committee:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const gossipQueueOpts: {[K in GossipType]: Pick<JobQueueOpts, "maxLength" | "typ
[GossipType.sync_committee]: {maxLength: 4096, type: QueueType.LIFO, maxConcurrency: 64},
[GossipType.light_client_finality_update]: {maxLength: 1024, type: QueueType.FIFO},
[GossipType.light_client_optimistic_update]: {maxLength: 1024, type: QueueType.FIFO},
[GossipType.beacon_block_and_blobs_sidecar]: {maxLength: 1024, type: QueueType.FIFO},
};

/**
Expand Down
3 changes: 3 additions & 0 deletions packages/beacon-node/test/spec/presets/fork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CachedBeaconStateBellatrix,
CachedBeaconStateAltair,
CachedBeaconStatePhase0,
CachedBeaconStateCapella,
} from "@lodestar/state-transition";
import * as slotFns from "@lodestar/state-transition/slot";
import {phase0, ssz} from "@lodestar/types";
Expand All @@ -29,6 +30,8 @@ export const fork: TestRunnerFn<ForkStateCase, BeaconStateAllForks> = (forkNext)
return slotFns.upgradeStateToBellatrix(preState as CachedBeaconStateAltair);
case ForkName.capella:
return slotFns.upgradeStateToCapella(preState as CachedBeaconStateBellatrix);
case ForkName.eip4844:
return slotFns.upgradeStateTo4844(preState as CachedBeaconStateCapella);
}
},
options: {
Expand Down
2 changes: 2 additions & 0 deletions packages/state-transition/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export {
CachedBeaconStatePhase0,
CachedBeaconStateAltair,
CachedBeaconStateBellatrix,
CachedBeaconStateCapella,
CachedBeaconState4844,
CachedBeaconStateAllForks,
CachedBeaconStateExecutions,
// Non-cached states
Expand Down
81 changes: 48 additions & 33 deletions packages/types/src/eip4844/sszTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import {ssz as phase0Ssz} from "../phase0/index.js";
import {ssz as altairSsz} from "../altair/index.js";
import {ssz as capellaSsz} from "../capella/index.js";

const {UintNum64, Slot, ValidatorIndex, Root, BLSSignature, UintBn256, Bytes32, Bytes48} = primitiveSsz;
const {UintNum64, Slot, Root, BLSSignature, UintBn256, Bytes32, Bytes48} = primitiveSsz;

// Custom Types
// https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/beacon-chain.md#custom-types

export const VersionedHash = Bytes32;
export const KZGCommitment = Bytes48;
export const KZGProof = Bytes48;
export const BLSFieldElement = Bytes32;
export const Blob = new ListCompositeType(BLSFieldElement, FIELD_ELEMENTS_PER_BLOB);
export const BlobKzgCommitments = new ListCompositeType(KZGCommitment, MAX_BLOBS_PER_BLOCK);
Expand All @@ -37,13 +38,13 @@ export const ExecutionPayloadHeader = new ContainerType(
{typeName: "ExecutionPayloadHeader", jsonCase: "eth2"}
);

// Annoyingly, we have to preserve Fields ordering while changing the type of ExecutionPayload
// We have to preserve Fields ordering while changing the type of ExecutionPayload
export const BeaconBlockBody = new ContainerType(
{
...altairSsz.BeaconBlockBody.fields,
executionPayload: ExecutionPayload, // Modified in EIP-4844
blsToExecutionChanges: capellaSsz.BeaconBlockBody.fields.blsToExecutionChanges,
blobKzgCommitments: BlobKzgCommitments,
blobKzgCommitments: BlobKzgCommitments, // New in EIP-4844
},
{typeName: "BeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true}
);
Expand All @@ -64,7 +65,49 @@ export const SignedBeaconBlock = new ContainerType(
{typeName: "SignedBeaconBlock", jsonCase: "eth2"}
);

// we don't reuse capella.BeaconState fields since we need to replace
export const BlobsSidecar = new ContainerType(
{
beaconBlockRoot: Root,
beaconBlockSlot: Slot,
blobs: new ListCompositeType(Blob, MAX_BLOBS_PER_BLOCK),
kzgAggregatedProof: KZGProof,
},
{typeName: "BlobsSidecar", jsonCase: "eth2"}
);

export const SignedBeaconBlockAndBlobsSidecar = new ContainerType(
{
beaconBlock: SignedBeaconBlock,
blobsSidecar: BlobsSidecar,
},
{typeName: "SignedBeaconBlockAndBlobsSidecar", jsonCase: "eth2"}
);

export const BlindedBeaconBlockBody = new ContainerType(
{
...BeaconBlockBody.fields,
executionPayloadHeader: ExecutionPayloadHeader, // Modified in EIP-4844
},
{typeName: "BlindedBeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const BlindedBeaconBlock = new ContainerType(
{
...capellaSsz.BlindedBeaconBlock.fields,
body: BlindedBeaconBlockBody, // Modified in EIP-4844
},
{typeName: "BlindedBeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const SignedBlindedBeaconBlock = new ContainerType(
{
message: BlindedBeaconBlock, // Modified in EIP-4844
signature: BLSSignature,
},
{typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"}
);

// We don't spread capella.BeaconState fields since we need to replace
// latestExecutionPayloadHeader and we cannot keep order doing that
export const BeaconState = new ContainerType(
{
Expand Down Expand Up @@ -101,39 +144,11 @@ export const BeaconState = new ContainerType(
currentSyncCommittee: altairSsz.SyncCommittee,
nextSyncCommittee: altairSsz.SyncCommittee,
// Execution
latestExecutionPayloadHeader: ExecutionPayloadHeader, // [Modified in EIP-4844]
latestExecutionPayloadHeader: ExecutionPayloadHeader, // Modified in EIP-4844
// Withdrawals
withdrawalQueue: capellaSsz.WithdrawalQueue,
nextWithdrawalIndex: capellaSsz.BeaconState.fields.nextWithdrawalIndex,
nextPartialWithdrawalValidatorIndex: capellaSsz.BeaconState.fields.nextPartialWithdrawalValidatorIndex,
},
{typeName: "BeaconState", jsonCase: "eth2"}
);

export const BlindedBeaconBlockBody = new ContainerType(
{
...BeaconBlockBody.fields,
executionPayloadHeader: ExecutionPayloadHeader, // Modified in EIP-4844
},
{typeName: "BlindedBeaconBlockBody", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const BlindedBeaconBlock = new ContainerType(
{
slot: Slot,
proposerIndex: ValidatorIndex,
// Reclare expandedType() with altair block and altair state
parentRoot: Root,
stateRoot: Root,
body: BlindedBeaconBlockBody, // Modified in EIP-4844
},
{typeName: "BlindedBeaconBlock", jsonCase: "eth2", cachePermanentRootStruct: true}
);

export const SignedBlindedBeaconBlock = new ContainerType(
{
message: BlindedBeaconBlock, // Modified in EIP-4844
signature: BLSSignature,
},
{typeName: "SignedBlindedBeaconBlock", jsonCase: "eth2"}
);
2 changes: 2 additions & 0 deletions packages/types/src/eip4844/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export type ExecutionPayloadHeader = ValueOf<typeof ssz.ExecutionPayloadHeader>;
export type BeaconBlockBody = ValueOf<typeof ssz.BeaconBlockBody>;
export type BeaconBlock = ValueOf<typeof ssz.BeaconBlock>;
export type SignedBeaconBlock = ValueOf<typeof ssz.SignedBeaconBlock>;
export type SignedBeaconBlockAndBlobsSidecar = ValueOf<typeof ssz.SignedBeaconBlockAndBlobsSidecar>;

export type BeaconState = ValueOf<typeof ssz.BeaconState>;

export type BlindedBeaconBlockBody = ValueOf<typeof ssz.BlindedBeaconBlockBody>;
Expand Down

0 comments on commit e6c13e6

Please sign in to comment.