diff --git a/packages/beacon-node/src/chain/archiveStore/historicalState/getHistoricalState.ts b/packages/beacon-node/src/chain/archiveStore/historicalState/getHistoricalState.ts index b24b09021cfd..341c49bf3bf4 100644 --- a/packages/beacon-node/src/chain/archiveStore/historicalState/getHistoricalState.ts +++ b/packages/beacon-node/src/chain/archiveStore/historicalState/getHistoricalState.ts @@ -54,7 +54,8 @@ export async function getHistoricalState( for await (const block of db.blockArchive.valuesStream({gt: state.slot, lte: slot})) { try { state = state.stateTransition( - block, + config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block), + false, { verifyProposer: false, verifySignatures: false, diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index c420c661cc73..a34f47bc05d9 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -181,6 +181,7 @@ export async function verifyBlocksInEpoch( this.logger, this.metrics, this.validatorMonitor, + this.serializedCache, abortController.signal, opts ), diff --git a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts index 8a9ffa3b1fed..dd5888541535 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlocksStateTransitionOnly.ts @@ -4,9 +4,11 @@ import { IBeaconStateView, StateHashTreeRootSource, } from "@lodestar/state-transition"; +import {sszTypesFor} from "@lodestar/types"; import {ErrorAborted, Logger, byteArrayEquals} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; import {nextEventLoop} from "../../util/eventLoop.js"; +import {SerializedCache} from "../../util/serializedCache.js"; import {BlockError, BlockErrorCode} from "../errors/index.js"; import {BlockProcessOpts} from "../options.js"; import {ValidatorMonitor} from "../validatorMonitor.js"; @@ -28,6 +30,7 @@ export async function verifyBlocksStateTransitionOnly( logger: Logger, metrics: Metrics | null, validatorMonitor: ValidatorMonitor | null, + serializedCache: SerializedCache, signal: AbortSignal, opts: BlockProcessOpts & ImportBlockOpts ): Promise<{postStates: IBeaconStateView[]; proposerBalanceDeltas: number[]; verifyStateTime: number}> { @@ -37,7 +40,8 @@ export async function verifyBlocksStateTransitionOnly( for (let i = 0; i < blocks.length; i++) { const {validProposerSignature, validSignatures} = opts; - const block = blocks[i].getBlock(); + const blockInput = blocks[i]; + const block = blockInput.getBlock(); const preState = i === 0 ? preState0 : postStates[i - 1]; const dataAvailabilityStatus = dataAvailabilityStatuses[i]; @@ -45,7 +49,11 @@ export async function verifyBlocksStateTransitionOnly( // NOTE: `regen.getPreState()` should have dialed forward the state already caching checkpoint states const useBlsBatchVerify = !opts?.disableBlsBatchVerify; const postState = preState.stateTransition( - block, + // We should have the serialized block from gossip in the hot path. + // Otherwise, fall back to serializing from block for the less latency critical paths like + // syncing/download. + serializedCache.get(block) ?? sszTypesFor(blockInput.forkName).SignedBeaconBlock.serialize(block), + false, { // NOTE: Assume valid for now while sending payload to execution engine in parallel // Latter verifyBlocksInEpoch() will make sure that payload is indeed valid diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index caf578cbe47a..131bd1058377 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -13,6 +13,7 @@ import { isForkPostGloas, } from "@lodestar/params"; import { + EMPTY_SIGNATURE, EffectiveBalanceIncrements, EpochShuffling, IBeaconStateView, @@ -34,6 +35,7 @@ import { Root, RootHex, SignedBeaconBlock, + SignedBlindedBeaconBlock, Slot, Status, UintNum64, @@ -406,6 +408,7 @@ export class BeaconChain implements IBeaconChain { blockStateCache, checkpointStateCache, seenBlockInputCache: this.seenBlockInputCache, + serializedCache: this.serializedCache, db, metrics, validatorMonitor, @@ -1110,7 +1113,20 @@ export class BeaconChain implements IBeaconChain { body, } as AssembledBlockType; - const {newStateRoot, proposerReward} = computeNewStateRoot(this.metrics, state, block); + const isBlinded = isBlindedBeaconBlock(block); + + // Build the serialized block and set signature to zero + // to re-use stateTransition() function which requires signed blocks + const serializedBlock = isBlinded + ? this.config.getPostBellatrixForkTypes(slot).SignedBlindedBeaconBlock.serialize({ + message: block as BlindedBeaconBlock, + signature: EMPTY_SIGNATURE, + } as SignedBlindedBeaconBlock) + : this.config.getForkTypes(slot).SignedBeaconBlock.serialize({ + message: block as BeaconBlock, + signature: EMPTY_SIGNATURE, + } as SignedBeaconBlock); + const {newStateRoot, proposerReward} = computeNewStateRoot(this.metrics, state, serializedBlock, isBlinded); block.stateRoot = newStateRoot; const blockRoot = produceResult.type === BlockType.Full diff --git a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts index 37ede767a88c..9e10c67c16bc 100644 --- a/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts +++ b/packages/beacon-node/src/chain/produceBlock/computeNewStateRoot.ts @@ -4,8 +4,7 @@ import { IBeaconStateView, StateHashTreeRootSource, } from "@lodestar/state-transition"; -import {BeaconBlock, BlindedBeaconBlock, Gwei, Root} from "@lodestar/types"; -import {ZERO_HASH} from "../../constants/index.js"; +import {Gwei, Root} from "@lodestar/types"; import {Metrics} from "../../metrics/index.js"; /** @@ -16,13 +15,12 @@ import {Metrics} from "../../metrics/index.js"; export function computeNewStateRoot( metrics: Metrics | null, state: IBeaconStateView, - block: BeaconBlock | BlindedBeaconBlock + blockBytes: Uint8Array, + isBlinded: boolean ): {newStateRoot: Root; proposerReward: Gwei; postState: IBeaconStateView} { - // Set signature to zero to re-use stateTransition() function which requires the SignedBeaconBlock type - const blockEmptySig = {message: block, signature: ZERO_HASH}; - const postState = state.stateTransition( - blockEmptySig, + blockBytes, + isBlinded, { // ExecutionPayloadStatus.valid: Assume payload valid, it has been produced by a trusted EL executionPayloadStatus: ExecutionPayloadStatus.valid, diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index a7de84a7061b..8061000d0762 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -14,6 +14,7 @@ import {Logger, fromHex, toRootHex} from "@lodestar/utils"; import {IBeaconDb} from "../../db/index.js"; import {Metrics} from "../../metrics/index.js"; import {nextEventLoop} from "../../util/eventLoop.js"; +import {SerializedCache} from "../../util/serializedCache.js"; import {getCheckpointFromState} from "../blocks/utils/checkpoint.js"; import {ChainEvent, ChainEventEmitter} from "../emitter.js"; import {SeenBlockInput} from "../seenCache/seenGossipBlockInput.js"; @@ -28,6 +29,7 @@ export type RegenModules = { blockStateCache: BlockStateCache; checkpointStateCache: CheckpointStateCache; seenBlockInputCache: SeenBlockInput; + serializedCache: SerializedCache; config: ChainForkConfig; emitter: ChainEventEmitter; logger: Logger; @@ -235,7 +237,9 @@ export class StateRegenerator implements IStateRegeneratorInternal { // We are only running the state transition to get a specific state's data. // stateTransition() does the clone() inside, transfer cache to make the regen faster state = state.stateTransition( - block, + this.modules.serializedCache.get(block) ?? + this.modules.config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block), + false, { // Replay previously imported blocks, assume valid and available executionPayloadStatus: ExecutionPayloadStatus.valid, diff --git a/packages/beacon-node/test/spec/presets/finality.test.ts b/packages/beacon-node/test/spec/presets/finality.test.ts index 1c86dd6c9cab..442b69f4199c 100644 --- a/packages/beacon-node/test/spec/presets/finality.test.ts +++ b/packages/beacon-node/test/spec/presets/finality.test.ts @@ -3,9 +3,9 @@ import {getConfig} from "@lodestar/config/test-utils"; import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; import { BeaconStateAllForks, + BeaconStateView, DataAvailabilityStatus, ExecutionPayloadStatus, - stateTransition, } from "@lodestar/state-transition"; import {altair, bellatrix, ssz} from "@lodestar/types"; import {createCachedBeaconStateTest} from "../../utils/cachedBeaconState.js"; @@ -18,24 +18,29 @@ import {RunnerType, TestRunnerFn, shouldVerify} from "../utils/types.js"; const finality: TestRunnerFn = (fork) => { return { testFunction: (testcase) => { - let state = createCachedBeaconStateTest(testcase.pre, getConfig(fork)); + let state = new BeaconStateView(createCachedBeaconStateTest(testcase.pre, getConfig(fork))); const verify = shouldVerify(testcase); for (let i = 0; i < testcase.meta.blocks_count; i++) { const signedBlock = testcase[`blocks_${i}`] as bellatrix.SignedBeaconBlock; - state = stateTransition(state, signedBlock, { - // Should assume payload valid and blob data available for this test - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailabilityStatus: DataAvailabilityStatus.Available, - verifyStateRoot: false, - verifyProposer: verify, - verifySignatures: verify, - assertCorrectProgressiveBalances, - }); + state = state.stateTransition( + state.cachedState.config.getForkTypes(signedBlock.message.slot).SignedBeaconBlock.serialize(signedBlock), + false, + { + // Should assume payload valid and blob data available for this test + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + verifyStateRoot: false, + verifyProposer: verify, + verifySignatures: verify, + assertCorrectProgressiveBalances, + }, + {} + ) as BeaconStateView; } - state.commit(); - return state; + state.cachedState.commit(); + return state.cachedState; }, options: { inputTypes: inputTypeSszTreeViewDU, diff --git a/packages/beacon-node/test/spec/presets/sanity.test.ts b/packages/beacon-node/test/spec/presets/sanity.test.ts index 1d35ab48ecf8..db73da74dc92 100644 --- a/packages/beacon-node/test/spec/presets/sanity.test.ts +++ b/packages/beacon-node/test/spec/presets/sanity.test.ts @@ -4,10 +4,10 @@ import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; import {InputType} from "@lodestar/spec-test-util"; import { BeaconStateAllForks, + BeaconStateView, DataAvailabilityStatus, ExecutionPayloadStatus, processSlots, - stateTransition, } from "@lodestar/state-transition"; import {SignedBeaconBlock, deneb, ssz} from "@lodestar/types"; import {bnToNum} from "@lodestar/utils"; @@ -60,21 +60,29 @@ const sanityBlocks: TestRunnerFn = (f return { testFunction: (testcase) => { const stateTB = testcase.pre; - let wrappedState = createCachedBeaconStateTest(stateTB, getConfig(fork)); + let wrappedState = new BeaconStateView(createCachedBeaconStateTest(stateTB, getConfig(fork))); const verify = shouldVerify(testcase); for (let i = 0; i < testcase.meta.blocks_count; i++) { const signedBlock = testcase[`blocks_${i}`] as deneb.SignedBeaconBlock; - wrappedState = stateTransition(wrappedState, signedBlock, { - // Assume valid and available for this test - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailabilityStatus: DataAvailabilityStatus.Available, - verifyStateRoot: verify, - verifyProposer: verify, - verifySignatures: verify, - assertCorrectProgressiveBalances, - }); + + wrappedState = wrappedState.stateTransition( + wrappedState.cachedState.config + .getForkTypes(signedBlock.message.slot) + .SignedBeaconBlock.serialize(signedBlock), + false, + { + // Assume valid and available for this test + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + verifyStateRoot: verify, + verifyProposer: verify, + verifySignatures: verify, + assertCorrectProgressiveBalances, + }, + {} + ) as BeaconStateView; } - return wrappedState; + return wrappedState.cachedState; }, options: { inputTypes: inputTypeSszTreeViewDU, diff --git a/packages/beacon-node/test/spec/presets/transition.test.ts b/packages/beacon-node/test/spec/presets/transition.test.ts index ea411ede2b0a..aaef2c75706a 100644 --- a/packages/beacon-node/test/spec/presets/transition.test.ts +++ b/packages/beacon-node/test/spec/presets/transition.test.ts @@ -4,9 +4,9 @@ import {config} from "@lodestar/config/default"; import {ACTIVE_PRESET, ForkName} from "@lodestar/params"; import { BeaconStateAllForks, + BeaconStateView, DataAvailabilityStatus, ExecutionPayloadStatus, - stateTransition, } from "@lodestar/state-transition"; import {SignedBeaconBlock, ssz} from "@lodestar/types"; import {bnToNum} from "@lodestar/utils"; @@ -51,20 +51,26 @@ const transition = const forkEpoch = bnToNum(meta.fork_epoch); const testConfig = createChainForkConfig(getTransitionConfig(forkNext, forkEpoch)); - let state = createCachedBeaconStateTest(testcase.pre, testConfig); + let state = new BeaconStateView(createCachedBeaconStateTest(testcase.pre, testConfig)); for (let i = 0; i < meta.blocks_count; i++) { const signedBlock = testcase[`blocks_${i}`] as SignedBeaconBlock; - state = stateTransition(state, signedBlock, { - // Assume valid and available for this test - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailabilityStatus: DataAvailabilityStatus.Available, - verifyStateRoot: true, - verifyProposer: false, - verifySignatures: false, - assertCorrectProgressiveBalances, - }); + + state = state.stateTransition( + testConfig.getForkTypes(signedBlock.message.slot).SignedBeaconBlock.serialize(signedBlock), + false, + { + // Assume valid and available for this test + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + verifyStateRoot: true, + verifyProposer: false, + verifySignatures: false, + assertCorrectProgressiveBalances, + }, + {} + ) as BeaconStateView; } - return state; + return state.cachedState; }, options: { inputTypes: inputTypeSszTreeViewDU, diff --git a/packages/beacon-node/test/spec/utils/gossipValidation.ts b/packages/beacon-node/test/spec/utils/gossipValidation.ts index 448cad42b2e1..229fd2a4ded5 100644 --- a/packages/beacon-node/test/spec/utils/gossipValidation.ts +++ b/packages/beacon-node/test/spec/utils/gossipValidation.ts @@ -295,7 +295,8 @@ function computePostState( fork: ForkName ): IBeaconStateView { return parentState.stateTransition( - signedBlock, + sszTypesFor(fork).SignedBeaconBlock.serialize(signedBlock), + false, { verifyStateRoot: true, verifyProposer: true, diff --git a/packages/state-transition/src/stateTransition.ts b/packages/state-transition/src/stateTransition.ts index e803ef90d765..5702101bd46c 100644 --- a/packages/state-transition/src/stateTransition.ts +++ b/packages/state-transition/src/stateTransition.ts @@ -1,5 +1,5 @@ import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {Epoch, SignedBeaconBlock, SignedBlindedBeaconBlock, Slot, ssz} from "@lodestar/types"; +import {Epoch, Slot, ssz} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {BlockExternalData, DataAvailabilityStatus, ExecutionPayloadStatus} from "./block/externalData.js"; import {processBlock} from "./block/index.js"; @@ -83,7 +83,8 @@ export enum StateHashTreeRootSource { */ export function stateTransition( state: CachedBeaconStateAllForks, - signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock, + signedBlockBytes: Uint8Array, + isBlinded: boolean, options: StateTransitionOpts = { // Assume default to be valid and available executionPayloadStatus: ExecutionPayloadStatus.valid, @@ -93,8 +94,11 @@ export function stateTransition( ): CachedBeaconStateAllForks { const {verifyStateRoot = true, verifyProposer = true} = options; + const blockSlot = readSignedBlockSlot(signedBlockBytes); + const signedBlock = isBlinded + ? state.config.getPostBellatrixForkTypes(blockSlot).SignedBlindedBeaconBlock.deserialize(signedBlockBytes) + : state.config.getForkTypes(blockSlot).SignedBeaconBlock.deserialize(signedBlockBytes); const block = signedBlock.message; - const blockSlot = block.slot; // .clone() before mutating state in state transition let postState = state.clone(options.dontTransferCache); @@ -154,6 +158,20 @@ export function stateTransition( return postState; } +function readSignedBlockSlot(signedBlockBytes: Uint8Array): Slot { + if (signedBlockBytes.byteLength < 12) { + throw Error("Invalid signed block bytes: too short to read message offset and slot"); + } + + const view = new DataView(signedBlockBytes.buffer, signedBlockBytes.byteOffset, signedBlockBytes.byteLength); + const messageOffset = view.getUint32(0, true); + if (messageOffset + 8 > signedBlockBytes.byteLength) { + throw Error("Invalid signed block bytes: message offset out of range"); + } + + return Number(view.getBigUint64(messageOffset, true)); +} + /** * Like `processSlots` from the spec but additionally handles fork upgrades * diff --git a/packages/state-transition/src/stateView/beaconStateView.ts b/packages/state-transition/src/stateView/beaconStateView.ts index 3c27e85f2ed9..4f72f06105bc 100644 --- a/packages/state-transition/src/stateView/beaconStateView.ts +++ b/packages/state-transition/src/stateView/beaconStateView.ts @@ -14,8 +14,6 @@ import { ExecutionPayloadHeader, Root, RootHex, - SignedBeaconBlock, - SignedBlindedBeaconBlock, Slot, SyncCommittee, ValidatorIndex, @@ -819,11 +817,15 @@ export class BeaconStateView implements IBeaconStateViewLatestFork { // State transition stateTransition( - signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock, + signedBlockBytes: Uint8Array, + isBlinded: boolean, options: StateTransitionOpts, {metrics, validatorMonitor}: StateTransitionModules ): IBeaconStateView { - const newState = stateTransition(this.cachedState, signedBlock, options, {metrics, validatorMonitor}); + const newState = stateTransition(this.cachedState, signedBlockBytes, isBlinded, options, { + metrics, + validatorMonitor, + }); return new BeaconStateView(newState); } diff --git a/packages/state-transition/src/stateView/interface.ts b/packages/state-transition/src/stateView/interface.ts index b0425279d05c..557834aa6e92 100644 --- a/packages/state-transition/src/stateView/interface.ts +++ b/packages/state-transition/src/stateView/interface.ts @@ -29,8 +29,6 @@ import { ExecutionPayloadHeader, Root, RootHex, - SignedBeaconBlock, - SignedBlindedBeaconBlock, Slot, ValidatorIndex, altair, @@ -160,7 +158,8 @@ export interface IBeaconStateView { // State transition stateTransition( - signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock, + signedBlockBytes: Uint8Array, + isBlinded: boolean, options: StateTransitionOpts, modules: StateTransitionModules ): IBeaconStateView; @@ -292,8 +291,10 @@ export type IBeaconStateViewLatestFork = Omit< * `NativeBeaconStateView` lifts it back to `BitArray` for beacon-node. * - Methods that produce another view (`stateTransition`, `processSlots`, * `loadOtherState`, `withParentPayloadApplied`) return `IBeaconStateViewNative` - * so callers can re-wrap without an `as unknown` cast. Param lists are reused - * via `Parameters<...>` to avoid duplicating signatures. + * so callers can re-wrap without an `as unknown` cast. + * + * Where possible, Param lists are reused via `Parameters<...>` to avoid duplicate signatures. + * Other incompatible native methods omit JS-only modules. * * The TS-side `BeaconStateView` also structurally satisfies this contract since * `BitArray` exposes `uint8Array` and `bitLen`. @@ -304,8 +305,15 @@ export type IBeaconStateViewNative = Omit< > & { executionPayloadAvailability: {uint8Array: Uint8Array; bitLen: number}; loadOtherState(...args: Parameters): IBeaconStateViewNative; - stateTransition(...args: Parameters): IBeaconStateViewNative; - processSlots(...args: Parameters): IBeaconStateViewNative; + stateTransition( + signedBlockBytes: Uint8Array, + isBlinded: boolean, + options: StateTransitionOpts + ): IBeaconStateViewNative; + processSlots( + slot: Slot, + epochTransitionCacheOpts?: EpochTransitionCacheOpts & {dontTransferCache?: boolean} + ): IBeaconStateViewNative; withParentPayloadApplied( ...args: Parameters ): IBeaconStateViewNative; diff --git a/packages/state-transition/src/stateView/nativeBeaconStateView.ts b/packages/state-transition/src/stateView/nativeBeaconStateView.ts index 60b8019776c3..414c9417e3f5 100644 --- a/packages/state-transition/src/stateView/nativeBeaconStateView.ts +++ b/packages/state-transition/src/stateView/nativeBeaconStateView.ts @@ -13,8 +13,6 @@ import { ExecutionPayloadHeader, Root, RootHex, - SignedBeaconBlock, - SignedBlindedBeaconBlock, Slot, SyncCommittee, ValidatorIndex, @@ -647,19 +645,20 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork { // State transition stateTransition( - signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock, + signedBlockBytes: Uint8Array, + isBlinded: boolean, options: StateTransitionOpts, - modules: StateTransitionModules + _modules: StateTransitionModules ): IBeaconStateView { - return new NativeBeaconStateView(this.binding.stateTransition(signedBlock, options, modules)); + return new NativeBeaconStateView(this.binding.stateTransition(signedBlockBytes, isBlinded, options)); } processSlots( slot: Slot, epochTransitionCacheOpts?: EpochTransitionCacheOpts & {dontTransferCache?: boolean}, - modules?: StateTransitionModules + _modules?: StateTransitionModules ): IBeaconStateView { - return new NativeBeaconStateView(this.binding.processSlots(slot, epochTransitionCacheOpts, modules)); + return new NativeBeaconStateView(this.binding.processSlots(slot, epochTransitionCacheOpts)); } // ─── altair ────────────────────────────────────────────────────────────── diff --git a/packages/state-transition/test/perf/block/processBlockAltair.test.ts b/packages/state-transition/test/perf/block/processBlockAltair.test.ts index eabceed23874..a16d82f79bf7 100644 --- a/packages/state-transition/test/perf/block/processBlockAltair.test.ts +++ b/packages/state-transition/test/perf/block/processBlockAltair.test.ts @@ -11,10 +11,10 @@ import { } from "@lodestar/params"; import {ssz} from "@lodestar/types"; import { + BeaconStateView, CachedBeaconStateAltair, DataAvailabilityStatus, ExecutionPayloadStatus, - stateTransition, } from "../../../src/index.js"; import { cachedStateAltairPopulateCaches, @@ -128,13 +128,18 @@ describe("altair processBlock", () => { return {state: stateCloned, block}; }, fn: ({state, block}) => { - const postState = stateTransition(state, block, { - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailabilityStatus: DataAvailabilityStatus.Available, - verifyProposer: false, - verifySignatures: false, - verifyStateRoot: false, - }); + const postState = new BeaconStateView(state).stateTransition( + state.config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block), + false, + { + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + }, + {} + ); // Not necessary to call commit here since it's called inside .stateTransition() diff --git a/packages/state-transition/test/perf/block/processBlockPhase0.test.ts b/packages/state-transition/test/perf/block/processBlockPhase0.test.ts index b3ea8d3459ec..dbece0f736d2 100644 --- a/packages/state-transition/test/perf/block/processBlockPhase0.test.ts +++ b/packages/state-transition/test/perf/block/processBlockPhase0.test.ts @@ -8,7 +8,7 @@ import { MAX_VOLUNTARY_EXITS, PresetName, } from "@lodestar/params"; -import {DataAvailabilityStatus, ExecutionPayloadStatus, stateTransition} from "../../../src/index.js"; +import {BeaconStateView, DataAvailabilityStatus, ExecutionPayloadStatus} from "../../../src/index.js"; import {generatePerfTestCachedStatePhase0, perfStateId} from "../../../src/testUtils/util.js"; import {StateBlock} from "../types.js"; import {BlockOpts, getBlockPhase0} from "./util.js"; @@ -108,15 +108,20 @@ describe("phase0 processBlock", () => { }, beforeEach: ({state, block}) => ({state: state.clone(), block}), fn: ({state, block}) => { - stateTransition(state, block, { - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailabilityStatus: DataAvailabilityStatus.Available, - verifyProposer: false, - verifySignatures: false, - verifyStateRoot: false, - }); + const postState = new BeaconStateView(state).stateTransition( + state.config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block), + false, + { + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + }, + {} + ); // set verifyStateRoot = false, and get the root here because the block root is wrong - state.hashTreeRoot(); + postState.hashTreeRoot(); }, }); } diff --git a/packages/state-transition/test/unit/rewards/blockRewards.test.ts b/packages/state-transition/test/unit/rewards/blockRewards.test.ts index e3d4347fceb2..e54732a50200 100644 --- a/packages/state-transition/test/unit/rewards/blockRewards.test.ts +++ b/packages/state-transition/test/unit/rewards/blockRewards.test.ts @@ -5,7 +5,7 @@ import {SYNC_COMMITTEE_SIZE} from "@lodestar/params"; import {ssz} from "@lodestar/types"; import {DataAvailabilityStatus, ExecutionPayloadStatus} from "../../../src/block/externalData.js"; import {computeBlockRewards} from "../../../src/rewards/blockRewards.js"; -import {stateTransition} from "../../../src/stateTransition.js"; +import {BeaconStateView} from "../../../src/stateView/beaconStateView.js"; import {cachedStateAltairPopulateCaches, generatePerfTestCachedStateAltair} from "../../../src/testUtils/util.js"; import {CachedBeaconStateAllForks} from "../../../src/types.js"; import {BlockAltairOpts, getBlockAltair} from "../../perf/block/util.js"; @@ -66,7 +66,7 @@ describe("chain / rewards / blockRewards", () => { id: "Attester slashing only", opts: { proposerSlashingLen: 0, - attesterSlashingLen: 5, + attesterSlashingLen: 2, attestationLen: 0, depositsLen: 0, voluntaryExitLen: 0, @@ -113,13 +113,18 @@ describe("chain / rewards / blockRewards", () => { expect(attesterSlashings).toBe(0); } - const postState = stateTransition(state as CachedBeaconStateAllForks, block, { - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailabilityStatus: DataAvailabilityStatus.Available, - verifyProposer: false, - verifySignatures: false, - verifyStateRoot: false, - }); + const postState = new BeaconStateView(state as CachedBeaconStateAllForks).stateTransition( + state.config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block), + false, + { + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + }, + {} + ); // Cross check with rewardCache const rewardCache = postState.proposerRewards; @@ -141,13 +146,18 @@ describe("chain / rewards / blockRewards", () => { preState.hashTreeRoot(); cachedStateAltairPopulateCaches(preState); - const postState = stateTransition(preState as CachedBeaconStateAllForks, block, { - executionPayloadStatus: ExecutionPayloadStatus.valid, - dataAvailabilityStatus: DataAvailabilityStatus.Available, - verifyProposer: false, - verifySignatures: false, - verifyStateRoot: false, - }); + const postState = new BeaconStateView(preState as CachedBeaconStateAllForks).stateTransition( + config.getForkTypes(block.message.slot).SignedBeaconBlock.serialize(block), + false, + { + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + }, + {} + ); // Set postState's reward cache const rewardCache = postState.proposerRewards; // Grab original reward cache before overwritten diff --git a/packages/state-transition/test/unit/stateView/nativeBeaconStateView.test.ts b/packages/state-transition/test/unit/stateView/nativeBeaconStateView.test.ts index f35facad5ad4..208675c30924 100644 --- a/packages/state-transition/test/unit/stateView/nativeBeaconStateView.test.ts +++ b/packages/state-transition/test/unit/stateView/nativeBeaconStateView.test.ts @@ -1,6 +1,8 @@ -import {describe, expect, it} from "vitest"; +import {describe, expect, it, vi} from "vitest"; import {BitArray} from "@chainsafe/ssz"; -import {IBeaconStateViewNative} from "../../../src/stateView/interface.js"; +import {DataAvailabilityStatus, ExecutionPayloadStatus} from "../../../src/index.ts"; +import type {StateTransitionOpts} from "../../../src/stateTransition.js"; +import type {IBeaconStateView, IBeaconStateViewNative} from "../../../src/stateView/interface.js"; import {NativeBeaconStateView} from "../../../src/stateView/nativeBeaconStateView.js"; describe("NativeBeaconStateView", () => { @@ -82,4 +84,32 @@ describe("NativeBeaconStateView", () => { expect(view.getBlockRootAtSlot(7)).toEqual(new Uint8Array([7])); expect(view.getBalance(2)).toBe(32_000_000_002); }); + + it("forwards serialized block bytes and blinded flag to native stateTransition", () => { + type NativeStateTransitionBytes = ( + blockBytes: Uint8Array, + isBlinded: boolean, + options: StateTransitionOpts + ) => IBeaconStateView; + + const blockBytes = new Uint8Array([1, 2, 3]); + const options: StateTransitionOpts = { + verifyStateRoot: false, + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailabilityStatus: DataAvailabilityStatus.Available, + }; + const postBinding = {} as IBeaconStateViewNative; + const binding = { + stateTransition: vi.fn(() => postBinding), + } as unknown as IBeaconStateViewNative; + + const view = new NativeBeaconStateView(binding) as NativeBeaconStateView & { + stateTransition: NativeStateTransitionBytes; + }; + const postState = view.stateTransition(blockBytes, true, options); + + expect(binding.stateTransition).toHaveBeenCalledWith(blockBytes, true, options); + expect(postState).toBeInstanceOf(NativeBeaconStateView); + expect((postState as NativeBeaconStateView).binding).toBe(postBinding); + }); });