Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: de-duplicate payloads from persisted beacon blocks #6029

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
5d0f20d
WIP with FullOrBlindedSignedBeaconBlock
dapplion Aug 5, 2023
607b8ab
fix(repositories): block type only change to FullOrBlinded
matthewkeil Aug 22, 2023
ccb61d2
Draft consumer points
dapplion Aug 5, 2023
2be6b6b
fix: update IBeaconChain and clear error in beaconBlocksByRoot
matthewkeil Aug 22, 2023
19c8031
wip: TODOs. remove commit
matthewkeil Aug 22, 2023
cbc892d
fix: rough out to get building
matthewkeil Aug 23, 2023
d6ccd9d
feat: move utils to fullOrBlindedBlock.ts
matthewkeil Aug 23, 2023
178399c
feat: build out fullOrBlindedBlock.ts
matthewkeil Aug 25, 2023
15f1125
feat: implement consumers of fullOrBlindedBlock.ts
matthewkeil Aug 25, 2023
788a992
test: fullOrBlindedBlock and add mocks
matthewkeil Aug 29, 2023
c140289
feat: block reassembly as generator
matthewkeil Aug 29, 2023
574c8ea
feat: make getBlockHeaders blinded safe
matthewkeil Aug 31, 2023
120ad32
test: debugging fullOrBlindedBlock
matthewkeil Aug 31, 2023
1ca3957
fix: bellatrix reassembleBlindedOrFullToFullBytes
matthewkeil Sep 5, 2023
bfea252
fix: capella reassembleBlindedOrFullToFullBytes
matthewkeil Sep 6, 2023
9e9dff4
test: debug fullOrBlindedBlock.ts
matthewkeil Sep 12, 2023
6626aaf
test: fix mocks and get mocks/block modifying for minimal and mainnet
matthewkeil Sep 13, 2023
3b67b2b
refactor: rename blindedBlockToFullBytes in chain/interface
matthewkeil Sep 13, 2023
935ad02
test: fix mocks and get mocks/block modifying for minimal and mainnet
matthewkeil Sep 13, 2023
a3dcbfb
feat: pull payload from execution layer
matthewkeil Sep 13, 2023
f89d715
chore: fix lint issues
matthewkeil Sep 13, 2023
90c453f
chore: fix check-types error
matthewkeil Sep 13, 2023
615e92e
fix: use toHexString for executionEngine call
matthewkeil Sep 13, 2023
5f0d231
fix: optional chain to fix .timestamp access error
matthewkeil Sep 15, 2023
19332e6
refactor: ensure "blindedOrFullBlock" to function names
matthewkeil Sep 18, 2023
1c4dcb9
test: add byteArrayEqualsThrowBadIndexes
matthewkeil Sep 18, 2023
147baee
refactor: standardize function names
matthewkeil Sep 18, 2023
21211e7
test: perf of not converting serialized
matthewkeil Oct 9, 2023
e377d22
refactor: update comment to be more clear
matthewkeil Oct 9, 2023
b48e3a8
refactor(beacon-node): make fullOrBlinded function names all consistent
matthewkeil Oct 9, 2023
cf81755
fix: add any in beacon-node/test/unit/fullOrBlindedBlock.test for che…
matthewkeil Oct 9, 2023
1d6c713
refactor: remove commented mocks that were updated
matthewkeil Oct 9, 2023
04d4065
chore: fix lint error
matthewkeil Oct 9, 2023
55786b3
refactor: simplify TransactionAndWithdrawals type
matthewkeil Oct 9, 2023
bc8ae5d
fix: remove comments making sure there are tests for fullOrBlinded in…
matthewkeil Oct 9, 2023
7840577
refactor: export chainConfig for use in tests that consume the mockBl…
matthewkeil Oct 9, 2023
fc02931
test: update workflow with new containers
matthewkeil Oct 10, 2023
ddb40af
test: revert workflow to original container versions
matthewkeil Oct 10, 2023
73bd529
feat: throw Eth1Error for invalid payload body
matthewkeil Oct 11, 2023
ecaf7f6
fix: convert all json to ssz fixtures
matthewkeil Oct 16, 2023
a2edbc1
fix: remove serialized blind/unblind code paths
matthewkeil Oct 16, 2023
4082549
chore: fix lint error
matthewkeil Oct 16, 2023
d1998ff
refactor: clean up fullOrBlindedBlock
matthewkeil Oct 16, 2023
2d00248
fix: debugged test:sim:multifork
matthewkeil Oct 17, 2023
16ece98
fix: debug broken test:sim:deneb
matthewkeil Oct 22, 2023
222a000
fix(workflows): comment out broken sim tests for now
matthewkeil Oct 22, 2023
c36a0c9
fix: rebase conflicts/updates
matthewkeil Jun 14, 2024
09e10b8
Merge branch 'unstable' into mkeil/dedup-beacon-block-2
matthewkeil Jun 29, 2024
798098e
feat: update types for FullOrBlindedBeaconBlock
matthewkeil Jun 29, 2024
ec0970c
feat: add FullOrBlindedSignedBeaconBlock to types
matthewkeil Jun 30, 2024
d4e8b9f
fix: update PR for new types PR
matthewkeil Jun 30, 2024
d5a07e6
Fix the types
nazarhussain Jul 1, 2024
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
30 changes: 21 additions & 9 deletions .github/workflows/test-sim-merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,30 @@ jobs:
- name: Pull Nethermind
run: docker pull $NETHERMIND_IMAGE

- name: Pull mergemock
run: docker pull $MERGEMOCK_IMAGE

- name: Test Lodestar <> mergemock relay
run: yarn test:sim:mergemock
- name: Test Lodestar <> Nethermind interop
run: yarn test:sim:merge-interop
working-directory: packages/beacon-node
env:
EL_BINARY_DIR: ${{ env.MERGEMOCK_IMAGE }}
EL_SCRIPT_DIR: mergemock
LODESTAR_PRESET: mainnet
EL_BINARY_DIR: ${{ env.NETHERMIND_IMAGE }}
EL_SCRIPT_DIR: netherminddocker
ENGINE_PORT: 8551
ETH_PORT: 8661
ETH_PORT: 8545

# This container is pre-shanghai and does not support enginer_getPayloadBodyV2
# for blinding/unblinding. Re-enable when we have a newer build.
#
# - name: Pull mergemock
# run: docker pull $MERGEMOCK_IMAGE

# - name: Test Lodestar <> mergemock relay
# run: yarn test:sim:mergemock
# working-directory: packages/beacon-node
# env:
# EL_BINARY_DIR: ${{ env.MERGEMOCK_IMAGE }}
# EL_SCRIPT_DIR: mergemock
# LODESTAR_PRESET: mainnet
# ENGINE_PORT: 8551
# ETH_PORT: 8661

- name: Upload debug log test files
if: ${{ always() }}
Expand Down
1 change: 1 addition & 0 deletions packages/beacon-node/src/api/impl/beacon/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export function getBeaconBlockApi({

return {
async getBlockHeaders({slot, parentRoot}) {
// TODO: (matthewkeil) Make this code BlindedOrFull block aware
// TODO - SLOW CODE: This code seems like it could be improved

// If one block in the response contains an optimistic block, mark the entire response as optimistic
Expand Down
21 changes: 18 additions & 3 deletions packages/beacon-node/src/api/impl/beacon/blocks/utils.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,38 @@
import {routes} from "@lodestar/api";
import {blockToHeader} from "@lodestar/state-transition";
import {ChainForkConfig} from "@lodestar/config";
import {SignedBeaconBlock} from "@lodestar/types";
import {GENESIS_SLOT} from "../../../../constants/index.js";
import {ApiError, ValidationError} from "../../errors.js";
import {IBeaconChain} from "../../../../chain/interface.js";
import {rootHexRegex} from "../../../../eth1/provider/utils.js";
import {isBlinded} from "../../../../util/fullOrBlindedBlock.js";

export function toBeaconHeaderResponse(
config: ChainForkConfig,
block: SignedBeaconBlock,
canonical = false
): routes.beacon.BlockHeaderResponse {
// need to have ts-ignore below to pull type here so it only happens once and
// gets used twice
const types = isBlinded(block)
? config.getExecutionForkTypes(block.message.slot)
: config.getForkTypes(block.message.slot);
return {
root: config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message),
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
root: types.BeaconBlock.hashTreeRoot(block.message),
canonical,
header: {
message: blockToHeader(config, block.message),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its cleaner to extend blockToHeader to accept full or blinded,

also then the root above can be calulated from the header returned by hashtree root of the blockheader ... it should be more efficient since body won't be merklized twice

signature: block.signature,
message: {
stateRoot: block.message.stateRoot,
proposerIndex: block.message.proposerIndex,
slot: block.message.slot,
parentRoot: block.message.parentRoot,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
bodyRoot: types.BeaconBlockBody.hashTreeRoot(block.message.body),
},
},
};
}
Expand Down
13 changes: 4 additions & 9 deletions packages/beacon-node/src/chain/blocks/writeBlockInputToDb.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {toHex} from "@lodestar/utils";
import {BeaconChain} from "../chain.js";
import {blindedOrFullBlockToBlinded} from "../../util/fullOrBlindedBlock.js";
import {BlockInput, BlockInputType} from "./types.js";

/**
Expand All @@ -13,17 +14,11 @@ export async function writeBlockInputToDb(this: BeaconChain, blocksInput: BlockI
const fnPromises: Promise<void>[] = [];

for (const blockInput of blocksInput) {
const {block, blockBytes} = blockInput;
const {block} = blockInput;
const blockRoot = this.config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message);
const blockRootHex = toHex(blockRoot);
if (blockBytes) {
// skip serializing data if we already have it
this.metrics?.importBlock.persistBlockWithSerializedDataCount.inc();
fnPromises.push(this.db.block.putBinary(this.db.block.getId(block), blockBytes));
} else {
this.metrics?.importBlock.persistBlockNoSerializedDataCount.inc();
fnPromises.push(this.db.block.add(block));
}
this.metrics?.importBlock.persistBlockNoSerializedDataCount.inc();
fnPromises.push(this.db.block.add(blindedOrFullBlockToBlinded(this.config, block)));
this.logger.debug("Persist block to hot DB", {
slot: block.message.slot,
root: blockRootHex,
Expand Down
62 changes: 59 additions & 3 deletions packages/beacon-node/src/chain/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
isBlindedBeaconBlock,
BeaconBlock,
SignedBeaconBlock,
FullOrBlindedSignedBeaconBlock,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did we reintroduce this type? Based on discussion in type refactor we don't want this type

ExecutionPayload,
BlindedBeaconBlock,
BlindedBeaconBlockBody,
Expand All @@ -47,6 +48,14 @@ import {Clock, ClockEvent, IClock} from "../util/clock.js";
import {ensureDir, writeIfNotExist} from "../util/file.js";
import {isOptimisticBlock} from "../util/forkChoice.js";
import {BufferPool} from "../util/bufferPool.js";
import {
blindedOrFullBlockToFull,
deserializeFullOrBlindedSignedBeaconBlock,
getEth1BlockHashFromSerializedBlock,
serializeFullOrBlindedSignedBeaconBlock,
} from "../util/fullOrBlindedBlock.js";
import {ExecutionPayloadBody} from "../execution/engine/types.js";
import {Eth1Error, Eth1ErrorCode} from "../eth1/errors.js";
import {BlockProcessor, ImportBlockOpts} from "./blocks/index.js";
import {ChainEventEmitter, ChainEvent} from "./emitter.js";
import {
Expand Down Expand Up @@ -510,7 +519,11 @@ export class BeaconChain implements IBeaconChain {
if (block) {
const data = await this.db.block.get(fromHexString(block.blockRoot));
if (data) {
return {block: data, executionOptimistic: isOptimisticBlock(block), finalized: false};
return {
block: await this.blindedOrFullBlockToFull(data),
executionOptimistic: isOptimisticBlock(block),
finalized: false,
};
}
}
// A non-finalized slot expected to be found in the hot db, could be archived during
Expand All @@ -519,7 +532,7 @@ export class BeaconChain implements IBeaconChain {
}

const data = await this.db.blockArchive.get(slot);
return data && {block: data, executionOptimistic: false, finalized: true};
return data && {block: await this.blindedOrFullBlockToFull(data), executionOptimistic: false, finalized: true};
}

async getBlockByRoot(
Expand All @@ -529,7 +542,11 @@ export class BeaconChain implements IBeaconChain {
if (block) {
const data = await this.db.block.get(fromHexString(root));
if (data) {
return {block: data, executionOptimistic: isOptimisticBlock(block), finalized: false};
return {
block: await this.blindedOrFullBlockToFull(data),
executionOptimistic: isOptimisticBlock(block),
finalized: false,
};
}
// If block is not found in hot db, try cold db since there could be an archive cycle happening
// TODO: Add a lock to the archiver to have deterministic behavior on where are blocks
Expand Down Expand Up @@ -574,6 +591,28 @@ export class BeaconChain implements IBeaconChain {
return this.produceBlockWrapper<BlockType.Blinded>(BlockType.Blinded, blockAttributes);
}

async blindedOrFullBlockToFull(block: FullOrBlindedSignedBeaconBlock): Promise<SignedBeaconBlock> {
const info = this.config.getForkInfo(block.message.slot);
return blindedOrFullBlockToFull(
this.config,
info.seq,
block,
await this.getTransactionsAndWithdrawals(info.seq, toHexString(block.message.body.eth1Data.blockHash))
);
}

async blindedOrFullBlockToFullBytes(forkSeq: ForkSeq, block: Uint8Array): Promise<Uint8Array> {
return serializeFullOrBlindedSignedBeaconBlock(
this.config,
blindedOrFullBlockToFull(
this.config,
forkSeq,
deserializeFullOrBlindedSignedBeaconBlock(this.config, block),
await this.getTransactionsAndWithdrawals(forkSeq, toHexString(getEth1BlockHashFromSerializedBlock(block)))
)
);
}

async produceBlockWrapper<T extends BlockType>(
blockType: T,
{
Expand Down Expand Up @@ -803,6 +842,23 @@ export class BeaconChain implements IBeaconChain {
}
}

private async getTransactionsAndWithdrawals(
forkSeq: ForkSeq,
blockHash: string
): Promise<Partial<ExecutionPayloadBody>> {
if (forkSeq < ForkSeq.bellatrix) {
return {};
}
const [payload] = await this.executionEngine.getPayloadBodiesByHash([blockHash]);
if (!payload) {
throw new Eth1Error(
{code: Eth1ErrorCode.INVALID_PAYLOAD_BODY, blockHash},
"payload body not found by eth1 engine"
);
}
return payload;
}

/**
* Regenerate state for attestation verification, this does not happen with default chain option of maxSkipSlots = 32 .
* However, need to handle just in case. Lodestar doesn't support multiple regen state requests for attestation verification
Expand Down
5 changes: 5 additions & 0 deletions packages/beacon-node/src/chain/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
BeaconBlock,
ExecutionPayload,
SignedBeaconBlock,
FullOrBlindedSignedBeaconBlock,
BlindedBeaconBlock,
} from "@lodestar/types";
import {
Expand All @@ -26,6 +27,7 @@ import {
import {BeaconConfig} from "@lodestar/config";
import {Logger} from "@lodestar/utils";
import {CheckpointWithHex, IForkChoice, ProtoBlock} from "@lodestar/fork-choice";
import {ForkSeq} from "@lodestar/params";
import {IEth1ForBlockProduction} from "../eth1/index.js";
import {IExecutionEngine, IExecutionBuilder} from "../execution/index.js";
import {Metrics} from "../metrics/metrics.js";
Expand Down Expand Up @@ -187,6 +189,9 @@ export interface IBeaconChain {
consensusBlockValue: Wei;
}>;

blindedOrFullBlockToFull(block: FullOrBlindedSignedBeaconBlock): Promise<SignedBeaconBlock>;
blindedOrFullBlockToFullBytes(forkSeq: ForkSeq, block: Uint8Array): Promise<Uint8Array>;

/** Process a block until complete */
processBlock(block: BlockInput, opts?: ImportBlockOpts): Promise<void>;
/** Process a chain of blocks until complete */
Expand Down
21 changes: 13 additions & 8 deletions packages/beacon-node/src/db/repositories/block.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import {ChainForkConfig} from "@lodestar/config";
import {Db, Repository} from "@lodestar/db";
import {SignedBeaconBlock, ssz} from "@lodestar/types";
import {ssz, FullOrBlindedSignedBeaconBlock} from "@lodestar/types";
import {blindedOrFullBlockHashTreeRoot} from "@lodestar/state-transition";
import {
deserializeFullOrBlindedSignedBeaconBlock,
serializeFullOrBlindedSignedBeaconBlock,
} from "../../util/fullOrBlindedBlock.js";
import {getSignedBlockTypeFromBytes} from "../../util/multifork.js";
import {Bucket, getBucketNameByValue} from "../buckets.js";

Expand All @@ -9,7 +14,7 @@ import {Bucket, getBucketNameByValue} from "../buckets.js";
*
* Used to store unfinalized blocks
*/
export class BlockRepository extends Repository<Uint8Array, SignedBeaconBlock> {
export class BlockRepository extends Repository<Uint8Array, FullOrBlindedSignedBeaconBlock> {
constructor(config: ChainForkConfig, db: Db) {
const bucket = Bucket.allForks_block;
const type = ssz.phase0.SignedBeaconBlock; // Pick some type but won't be used
Expand All @@ -19,15 +24,15 @@ export class BlockRepository extends Repository<Uint8Array, SignedBeaconBlock> {
/**
* Id is hashTreeRoot of unsigned BeaconBlock
*/
getId(value: SignedBeaconBlock): Uint8Array {
return this.config.getForkTypes(value.message.slot).BeaconBlock.hashTreeRoot(value.message);
getId(value: FullOrBlindedSignedBeaconBlock): Uint8Array {
return blindedOrFullBlockHashTreeRoot(this.config, value.message);
}

encodeValue(value: SignedBeaconBlock): Buffer {
return this.config.getForkTypes(value.message.slot).SignedBeaconBlock.serialize(value) as Buffer;
encodeValue(value: FullOrBlindedSignedBeaconBlock): Buffer {
return serializeFullOrBlindedSignedBeaconBlock(this.config, value) as Buffer;
}

decodeValue(data: Buffer): SignedBeaconBlock {
return getSignedBlockTypeFromBytes(this.config, data).deserialize(data);
decodeValue(data: Buffer): FullOrBlindedSignedBeaconBlock {
return deserializeFullOrBlindedSignedBeaconBlock(this.config, data);
}
}
Loading
Loading