Merge branch 'unstable' into te/peerDAS_merge_unstable_may_14

This commit is contained in:
Nico Flaig
2025-05-21 12:13:18 +01:00
67 changed files with 702 additions and 304 deletions

View File

@@ -0,0 +1,13 @@
participants:
- el_type: geth
cl_type: lodestar
cl_image: chainsafe/lodestar:kurtosis-ci
- el_type: geth
cl_type: lighthouse
additional_services:
- assertoor
assertoor_params:
run_block_proposal_check: true
run_transaction_test: true
run_blob_transaction_test: true
run_opcodes_transaction_test: true

27
.github/workflows/kurtosis.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Kurtosis sim tests
on:
workflow_dispatch:
schedule:
- cron: "0 0 * * *" # Runs at midnight UTC everyday
jobs:
test:
timeout-minutes: 60
name: Build and run Kurtosis
runs-on: buildjet-4vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build Docker image - local only
run: >
docker buildx build . --load \
--tag chainsafe/lodestar:kurtosis-ci \
--build-arg COMMIT=$(git rev-parse HEAD)
- name: Run test
uses: ethpandaops/kurtosis-assertoor-github-action@v1
with:
ethereum_package_args: '.github/workflows/assets/kurtosis_sim_test_config.yaml'

View File

@@ -4838,7 +4838,7 @@
"calcs": [],
"displayMode": "list",
"placement": "bottom",
"showLegend": false
"showLegend": true
},
"tooltip": {
"mode": "single",
@@ -4856,7 +4856,8 @@
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval]) / rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
"format": "heatmap",
"instant": false,
"legendFormat": "time",
"interval": "",
"legendFormat": "{{source}}",
"range": true,
"refId": "A"
}

View File

@@ -90,6 +90,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -218,10 +219,12 @@
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "auto"
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "10.1.1",
"pluginVersion": "10.4.1",
"targets": [
{
"datasource": {
@@ -287,10 +290,12 @@
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "value"
"textMode": "value",
"wideLayout": true
},
"pluginVersion": "10.1.1",
"pluginVersion": "10.4.1",
"targets": [
{
"datasource": {
@@ -339,10 +344,12 @@
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "name"
"textMode": "name",
"wideLayout": true
},
"pluginVersion": "10.1.1",
"pluginVersion": "10.4.1",
"targets": [
{
"datasource": {
@@ -394,10 +401,12 @@
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "name"
"textMode": "name",
"wideLayout": true
},
"pluginVersion": "10.1.1",
"pluginVersion": "10.4.1",
"targets": [
{
"datasource": {
@@ -413,7 +422,6 @@
}
],
"title": "Lodestar version",
"transformations": [],
"type": "stat"
},
{
@@ -449,10 +457,12 @@
"fields": "",
"values": false
},
"showPercentChange": false,
"text": {},
"textMode": "name"
"textMode": "name",
"wideLayout": true
},
"pluginVersion": "10.1.1",
"pluginVersion": "10.4.1",
"targets": [
{
"datasource": {
@@ -480,6 +490,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -692,6 +703,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -776,6 +788,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -948,6 +961,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -1029,6 +1043,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -1137,6 +1152,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -1220,6 +1236,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -1304,6 +1321,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -1365,10 +1383,12 @@
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"exemplar": false,
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval])\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum{source=\"gossip\"}[$rate_interval])\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count{source=\"gossip\"}[$rate_interval])",
"interval": "",
"legendFormat": "till received",
"range": true,
"refId": "A"
},
{
@@ -1398,6 +1418,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -1508,6 +1529,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -1588,6 +1610,7 @@
"mode": "palette-classic"
},
"custom": {
"axisBorderShow": false,
"axisCenteredZero": false,
"axisColorMode": "text",
"axisLabel": "",
@@ -8764,8 +8787,7 @@
}
],
"refresh": "10s",
"schemaVersion": 38,
"style": "dark",
"schemaVersion": 39,
"tags": [
"lodestar",
"debug"

View File

@@ -3327,7 +3327,7 @@
"exemplar": false,
"expr": "(\n rate(lodestar_gossip_block_elapsed_time_till_processed_sum[$rate_interval])\n -\n rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval])\n)\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
"interval": "",
"legendFormat": "processed minus received",
"legendFormat": "processed minus received {{source}}",
"range": true,
"refId": "A"
},
@@ -3421,10 +3421,12 @@
"type": "prometheus",
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"exemplar": false,
"expr": "rate(lodestar_gossip_block_elapsed_time_till_received_sum[$rate_interval])\n/\nrate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval])",
"interval": "",
"legendFormat": "till received",
"legendFormat": "till received {{source}}",
"range": true,
"refId": "A"
},
{
@@ -5370,8 +5372,8 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"0.5\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"legendFormat": "<=0.5s",
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"0.5\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"legendFormat": "<=0.5s {{source}}",
"range": true,
"refId": "A"
},
@@ -5381,9 +5383,9 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"1\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"1\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"hide": false,
"legendFormat": "<=1s",
"legendFormat": "<=1s {{source}}",
"range": true,
"refId": "B"
},
@@ -5393,9 +5395,9 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"2\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"2\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"hide": false,
"legendFormat": "<=2s",
"legendFormat": "<=2s {{source}}",
"range": true,
"refId": "C"
},
@@ -5405,9 +5407,10 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"4\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"4\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"hide": false,
"legendFormat": "<=4s",
"interval": "",
"legendFormat": "<=4s {{source}}",
"range": true,
"refId": "D"
},
@@ -5417,9 +5420,10 @@
"uid": "${DS_PROMETHEUS}"
},
"editorMode": "code",
"expr": "sum(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"6\"}[$rate_interval]))\n/\nsum(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"expr": "sum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_bucket{le=\"6\"}[$rate_interval]))\n/\nsum by (source)(rate(lodestar_gossip_block_elapsed_time_till_received_count[$rate_interval]))",
"hide": false,
"legendFormat": "<=6s",
"interval": "",
"legendFormat": "<=6s {{source}}",
"range": true,
"refId": "E"
}

View File

@@ -178,7 +178,7 @@
},
"editorMode": "code",
"exemplar": false,
"expr": "(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(network_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes))\nor\n(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes))",
"expr": "(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(network_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes or vector(0)))\nor\n(sum(nodejs_heap_size_total_bytes) + sum(discv5_worker_nodejs_heap_size_total_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_total_bytes or vector(0)))",
"hide": false,
"interval": "",
"legendFormat": "node allocated heap",
@@ -192,7 +192,7 @@
},
"editorMode": "code",
"exemplar": false,
"expr": "(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(network_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes)) \nor\n(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes)) ",
"expr": "(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(network_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes or vector(0))) \nor\n(sum(nodejs_heap_size_used_bytes) + sum(discv5_worker_nodejs_heap_size_used_bytes) + sum(lodestar_historical_state_worker_nodejs_heap_size_used_bytes or vector(0))) ",
"hide": false,
"interval": "",
"legendFormat": "node used heap",
@@ -206,7 +206,7 @@
},
"editorMode": "code",
"exemplar": false,
"expr": "(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(network_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes))\nor\n(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes))",
"expr": "(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(network_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes or vector(0)))\nor\n(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes or vector(0)))",
"hide": false,
"interval": "",
"legendFormat": "node external memory",
@@ -307,7 +307,7 @@
},
"editorMode": "code",
"exemplar": false,
"expr": "(nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + discv5_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + network_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + lodestar_historical_state_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"})\nor\n(nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + discv5_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + lodestar_historical_state_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"})",
"expr": "(sum(nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"}) + sum(discv5_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"}) + sum(network_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"}) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} or vector(0)))\nor\nsum(nodejs_external_memory_bytes{job=~\"$validator_job|validator\"})",
"hide": false,
"interval": "",
"legendFormat": "external_memory",
@@ -1196,7 +1196,7 @@
"refId": "C"
}
],
"title": "HIstorical State Worker Thread - GC pause time rate + reclaimed bytes",
"title": "Historical State Worker Thread - GC pause time rate + reclaimed bytes",
"type": "timeseries"
},
{

View File

@@ -2,7 +2,18 @@
This section of the documentation will cover common questions and common encounters by users and developers.
## Tooling
## Developer Tooling
### Python Distutils
:::note "MacOS: ModuleNotFoundError: No module named 'distutils'"
When using the `yarn` command, some MacOS users may experience this error if they are running Python 3.12+. The `distutils` module has been removed from the standard Python library via PEP 632 which deprecates and eliminates `distutils` in favor of other tools like `setuptools`.
For MacOS, there is no direct equivalent package to Linux's `python3-distutils`. Therefore, the solution is to install the `python-setuptools` package via Homebrew: `brew install python-setuptools`
:::
### Yarn Package Manager
:::note "Package manager issues"

View File

@@ -4,7 +4,7 @@
],
"npmClient": "yarn",
"useNx": true,
"version": "1.29.0",
"version": "1.30.0",
"stream": true,
"command": {
"version": {

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -72,10 +72,10 @@
"dependencies": {
"@chainsafe/persistent-merkle-tree": "^1.1.0",
"@chainsafe/ssz": "^1.2.0",
"@lodestar/config": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/config": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"eventsource": "^2.0.2",
"qs": "^6.11.1"
},

View File

@@ -1,5 +1,5 @@
import {ContainerType, ValueOf} from "@chainsafe/ssz";
import {ChainForkConfig} from "@lodestar/config";
import {ChainForkConfig, SpecJson} from "@lodestar/config";
import {ssz} from "@lodestar/types";
import {
ArrayOf,
@@ -24,7 +24,7 @@ export const ForkListType = ArrayOf(ssz.phase0.Fork);
export type DepositContract = ValueOf<typeof DepositContractType>;
export type ForkList = ValueOf<typeof ForkListType>;
export type Spec = Record<string, string>;
export type Spec = SpecJson;
export type Endpoints = {
/**

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -117,18 +117,18 @@
"@libp2p/peer-id": "^5.1.0",
"@libp2p/prometheus-metrics": "^4.3.15",
"@libp2p/tcp": "^10.1.8",
"@lodestar/api": "^1.29.0",
"@lodestar/config": "^1.29.0",
"@lodestar/db": "^1.29.0",
"@lodestar/fork-choice": "^1.29.0",
"@lodestar/light-client": "^1.29.0",
"@lodestar/logger": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/reqresp": "^1.29.0",
"@lodestar/state-transition": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/validator": "^1.29.0",
"@lodestar/api": "^1.30.0",
"@lodestar/config": "^1.30.0",
"@lodestar/db": "^1.30.0",
"@lodestar/fork-choice": "^1.30.0",
"@lodestar/light-client": "^1.30.0",
"@lodestar/logger": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/reqresp": "^1.30.0",
"@lodestar/state-transition": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"@lodestar/validator": "^1.30.0",
"@multiformats/multiaddr": "^12.1.3",
"c-kzg": "^4.1.0",
"datastore-core": "^10.0.2",

View File

@@ -15,7 +15,7 @@ import {specConstants} from "./constants.js";
* [altair](https://github.com/ethereum/consensus.0-specs/blob/v1.1.10/presets/mainnet/altair.yaml) values
* - Configuration for the beacon node, for example the [mainnet](https://github.com/ethereum/consensus-specs/blob/v1.1.10/configs/mainnet.yaml) values
*/
export function renderJsonSpec(config: ChainConfig): Record<string, string> {
export function renderJsonSpec(config: ChainConfig): routes.config.Spec {
const configJson = chainConfigToJson(config);
const presetJson = presetToJson(activePreset);
const constantsJson = specValuesToJson(specConstants);

View File

@@ -34,6 +34,7 @@ import {
BeaconBlock,
BlindedBeaconBlock,
BlockContents,
Bytes32,
CommitteeIndex,
Epoch,
ProducedBlockSource,
@@ -75,7 +76,7 @@ import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossi
import {CommitteeSubscription} from "../../../network/subnets/index.js";
import {SyncState} from "../../../sync/index.js";
import {isOptimisticBlock} from "../../../util/forkChoice.js";
import {getDefaultGraffiti, toGraffitiBuffer} from "../../../util/graffiti.js";
import {getDefaultGraffiti, toGraffitiBytes} from "../../../util/graffiti.js";
import {getLodestarClientVersion} from "../../../util/metadata.js";
import {ApiOptions} from "../../options.js";
import {getStateResponseWithRegen} from "../beacon/state/utils.js";
@@ -399,25 +400,15 @@ export function getValidatorApi(
async function produceBuilderBlindedBlock(
slot: Slot,
randaoReveal: BLSSignature,
graffiti?: string,
graffiti: Bytes32,
// as of now fee recipient checks can not be performed because builder does not return bid recipient
{
skipHeadChecksAndUpdate,
commonBlockBody,
parentBlockRoot: inParentBlockRoot,
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> &
(
| {
skipHeadChecksAndUpdate: true;
commonBlockBody: CommonBlockBody;
parentBlockRoot: Root;
}
| {
skipHeadChecksAndUpdate?: false | undefined;
commonBlockBody?: undefined;
parentBlockRoot?: undefined;
}
) = {}
parentBlockRoot,
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> & {
commonBlockBody: CommonBlockBody;
parentBlockRoot: Root;
}
): Promise<ProduceBlindedBlockRes> {
const version = config.getForkName(slot);
if (!isForkPostBellatrix(version)) {
@@ -435,17 +426,6 @@ export function getValidatorApi(
throw Error("Execution builder disabled");
}
let parentBlockRoot: Root;
if (skipHeadChecksAndUpdate !== true) {
notWhileSyncing();
await waitForSlot(slot); // Must never request for a future slot > currentSlot
parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot);
} else {
parentBlockRoot = inParentBlockRoot;
}
notOnOutOfRangeData(parentBlockRoot);
let timer: undefined | ((opts: {source: ProducedBlockSource}) => number);
try {
timer = metrics?.blockProductionTime.startTimer();
@@ -453,9 +433,7 @@ export function getValidatorApi(
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
),
graffiti,
commonBlockBody,
});
@@ -482,37 +460,20 @@ export function getValidatorApi(
async function produceEngineFullBlockOrContents(
slot: Slot,
randaoReveal: BLSSignature,
graffiti?: string,
graffiti: Bytes32,
{
feeRecipient,
strictFeeRecipientCheck,
skipHeadChecksAndUpdate,
commonBlockBody,
parentBlockRoot: inParentBlockRoot,
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> &
(
| {
skipHeadChecksAndUpdate: true;
commonBlockBody: CommonBlockBody;
parentBlockRoot: Root;
}
| {skipHeadChecksAndUpdate?: false | undefined; commonBlockBody?: undefined; parentBlockRoot?: undefined}
) = {}
parentBlockRoot,
}: Omit<routes.validator.ExtraProduceBlockOpts, "builderSelection"> & {
commonBlockBody: CommonBlockBody;
parentBlockRoot: Root;
}
): Promise<ProduceBlockOrContentsRes & {shouldOverrideBuilder?: boolean}> {
const source = ProducedBlockSource.engine;
metrics?.blockProductionRequests.inc({source});
let parentBlockRoot: Root;
if (skipHeadChecksAndUpdate !== true) {
notWhileSyncing();
await waitForSlot(slot); // Must never request for a future slot > currentSlot
parentBlockRoot = fromHex(chain.getProposerHead(slot).blockRoot);
} else {
parentBlockRoot = inParentBlockRoot;
}
notOnOutOfRangeData(parentBlockRoot);
let timer: undefined | ((opts: {source: ProducedBlockSource}) => number);
try {
timer = metrics?.blockProductionTime.startTimer();
@@ -520,9 +481,7 @@ export function getValidatorApi(
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
),
graffiti,
feeRecipient,
commonBlockBody,
});
@@ -614,6 +573,10 @@ export function getValidatorApi(
);
}
const graffitiBytes = toGraffitiBytes(
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
);
const loggerContext = {
slot,
fork,
@@ -630,9 +593,7 @@ export function getValidatorApi(
slot,
parentBlockRoot,
randaoReveal,
graffiti: toGraffitiBuffer(
graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts)
),
graffiti: graffitiBytes,
});
logger.debug("Produced common block body", loggerContext);
@@ -651,23 +612,19 @@ export function getValidatorApi(
// Start calls for building execution and builder blocks
const builderPromise = isBuilderEnabled
? produceBuilderBlindedBlock(slot, randaoReveal, graffiti, {
? produceBuilderBlindedBlock(slot, randaoReveal, graffitiBytes, {
feeRecipient,
// can't do fee recipient checks as builder bid doesn't return feeRecipient as of now
strictFeeRecipientCheck: false,
// skip checking and recomputing head in these individual produce calls
skipHeadChecksAndUpdate: true,
commonBlockBody,
parentBlockRoot,
})
: Promise.reject(new Error("Builder disabled"));
const enginePromise = isEngineEnabled
? produceEngineFullBlockOrContents(slot, randaoReveal, graffiti, {
? produceEngineFullBlockOrContents(slot, randaoReveal, graffitiBytes, {
feeRecipient,
strictFeeRecipientCheck,
// skip checking and recomputing head in these individual produce calls
skipHeadChecksAndUpdate: true,
commonBlockBody,
parentBlockRoot,
}).then((engineBlock) => {

View File

@@ -1,5 +1,5 @@
import {ModuleThread} from "@chainsafe/threads";
import {BeaconConfig} from "@lodestar/config";
import {BeaconConfig, SpecJson} from "@lodestar/config";
import {LoggerNode, LoggerNodeOpts} from "@lodestar/logger/node";
import {BeaconStateTransitionMetrics} from "@lodestar/state-transition";
import {Gauge, Histogram} from "@lodestar/utils";
@@ -20,7 +20,7 @@ export type HistoricalStateRegenModules = HistoricalStateRegenInitModules & {
};
export type HistoricalStateWorkerData = {
chainConfigJson: Record<string, string>;
chainConfigJson: SpecJson;
genesisValidatorsRoot: Uint8Array;
genesisTime: number;
maxConcurrency: number;

View File

@@ -39,8 +39,8 @@ export async function archiveBlocks(
): Promise<void> {
// Use fork choice to determine the blocks to archive and delete
// getAllAncestorBlocks response includes the finalized block, so it's also moved to the cold db
const finalizedCanonicalBlocks = forkChoice.getAllAncestorBlocks(finalizedCheckpoint.rootHex);
const finalizedNonCanonicalBlocks = forkChoice.getAllNonAncestorBlocks(finalizedCheckpoint.rootHex);
const {ancestors: finalizedCanonicalBlocks, nonAncestors: finalizedNonCanonicalBlocks} =
forkChoice.getAllAncestorAndNonAncestorBlocks(finalizedCheckpoint.rootHex);
// NOTE: The finalized block will be exactly the first block of `epoch` or previous
const finalizedPostDeneb = finalizedCheckpoint.epoch >= config.DENEB_FORK_EPOCH;

View File

@@ -18,6 +18,7 @@ import {
createCachedBeaconState,
getEffectiveBalanceIncrementsZeroInactive,
isCachedBeaconState,
processSlots,
} from "@lodestar/state-transition";
import {
BeaconBlock,
@@ -1256,12 +1257,14 @@ export class BeaconChain implements IBeaconChain {
}
async getBlockRewards(block: BeaconBlock | BlindedBeaconBlock): Promise<BlockRewards> {
const preState = this.regen.getPreStateSync(block);
let preState = this.regen.getPreStateSync(block);
if (preState === null) {
throw Error(`Pre-state is unavailable given block's parent root ${toRootHex(block.parentRoot)}`);
}
preState = processSlots(preState, block.slot); // Dial preState's slot to block.slot
const postState = this.regen.getStateSync(toRootHex(block.stateRoot)) ?? undefined;
return computeBlockRewards(block, preState.clone(), postState?.clone());
@@ -1297,12 +1300,14 @@ export class BeaconChain implements IBeaconChain {
block: BeaconBlock | BlindedBeaconBlock,
validatorIds?: (ValidatorIndex | string)[]
): Promise<SyncCommitteeRewards> {
const preState = this.regen.getPreStateSync(block);
let preState = this.regen.getPreStateSync(block);
if (preState === null) {
throw Error(`Pre-state is unavailable given block's parent root ${toRootHex(block.parentRoot)}`);
}
preState = processSlots(preState, block.slot); // Dial preState's slot to block.slot
return computeSyncCommitteeRewards(block, preState.clone(), validatorIds);
}
}

View File

@@ -44,7 +44,7 @@ import {
PayloadId,
getExpectedGasLimit,
} from "../../execution/index.js";
import {fromGraffitiBuffer} from "../../util/graffiti.js";
import {fromGraffitiBytes} from "../../util/graffiti.js";
import {ckzg} from "../../util/kzg.js";
import type {BeaconChain} from "../chain.js";
import {CommonBlockBody} from "../interface.js";
@@ -163,7 +163,7 @@ export async function produceBlockBody<T extends BlockType>(
} = blockBody;
Object.assign(logMeta, {
graffiti: fromGraffitiBuffer(graffiti),
graffiti: fromGraffitiBytes(graffiti),
attestations: attestations.length,
deposits: deposits.length,
voluntaryExits: voluntaryExits.length,

View File

@@ -5,10 +5,16 @@ import {Metrics} from "../../metrics/index.js";
import {isSuperSetOrEqual} from "../../util/bitArray.js";
/**
* With this gossip validation condition: [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
* Since ATTESTATION_PROPAGATION_SLOT_RANGE is 32, we keep seen AggregateAndProof in the last 2 epochs.
* With this gossip validation condition:
* pre-deneb:
* - [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
* post-deneb:
* - [IGNORE] the epoch of `aggregate.data.slot` is either the current or previous epoch
* - [IGNORE] `aggregate.data.slot` is equal to or earlier than the `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
*
* We keep seen AggregateAndProof in the last 2 epochs pre and post deneb.
*/
const MAX_EPOCHS_IN_CACHE = 2;
const EPOCH_LOOKBACK_LIMIT = 2;
export type AggregationInfo = {
aggregationBits: BitArray;
@@ -64,7 +70,7 @@ export class SeenAggregatedAttestations {
}
prune(currentEpoch: Epoch): void {
this.lowestPermissibleEpoch = Math.max(currentEpoch - MAX_EPOCHS_IN_CACHE, 0);
this.lowestPermissibleEpoch = Math.max(currentEpoch - EPOCH_LOOKBACK_LIMIT, 0);
for (const epoch of this.aggregateRootsByEpoch.keys()) {
if (epoch < this.lowestPermissibleEpoch) {
this.aggregateRootsByEpoch.delete(epoch);

View File

@@ -1,18 +1,27 @@
import {Epoch, ValidatorIndex} from "@lodestar/types";
import {MapDef} from "@lodestar/utils";
// The next, current and previous epochs. We require the next epoch due to the
// `MAXIMUM_GOSSIP_CLOCK_DISPARITY`. We require the previous epoch since the
// specification delcares:
// How many *non future* epochs we intend to keep for SeenAttesters.
// Pre and post deneb specs require us to accept attestations from current and
// previous epoch.
//
// ```
// aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE
// >= current_slot >= aggregate.data.slot
// ```
// Pre-deneb:
// - `attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot`
//
// This means that during the current epoch we will always accept an attestation
// from at least one slot in the previous epoch.
const MAX_EPOCHS = 3;
// Post-deneb:
// - `attestation.data.slot <= current_slot`
// - `compute_epoch_at_slot(attestation.data.slot) in (get_previous_epoch(state), get_current_epoch(state))`
//
// When factored in MAXIMUM_GOSSIP_CLOCK_DISPARITY, it is possible we keep 3 epochs of SeenAttesters:
// previous, current and future epoch. This constant is solely used to calculate `lowestPermissibleEpoch`
// which prunes anything older than it.
//
// Assuming we're at epoch 100 while all other nodes at epoch 99, they all accept attestations at epoch 98, 99.
// If MAX_RETAINED_EPOCH = 2 then our lowestPermissibleEpoch is 98 which is fine
//
// Assuming we're at epoch 99 while all other nodes at epoch 100, they all accept attestations at epoch 99, 100.
// If MAX_RETAINED_EPOCH = 2 then lowestPermissibleEpoch is 97 which is more than enough
const EPOCH_LOOKBACK_LIMIT = 2;
/**
* Keeps a cache to filter unaggregated attestations from the same validator in the same epoch.
@@ -34,7 +43,7 @@ export class SeenAttesters {
}
prune(currentEpoch: Epoch): void {
this.lowestPermissibleEpoch = Math.max(currentEpoch - MAX_EPOCHS, 0);
this.lowestPermissibleEpoch = Math.max(currentEpoch - EPOCH_LOOKBACK_LIMIT, 0);
for (const epoch of this.validatorIndexesByEpoch.keys()) {
if (epoch < this.lowestPermissibleEpoch) {
this.validatorIndexesByEpoch.delete(epoch);

View File

@@ -104,9 +104,17 @@ async function validateAggregateAndProof(
throw new AttestationError(GossipAction.REJECT, {code: AttestationErrorCode.BAD_TARGET_EPOCH});
}
// Pre-deneb:
// [IGNORE] aggregate.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (with a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
// -- i.e. aggregate.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= aggregate.data.slot
// (a client MAY queue future aggregates for processing at the appropriate slot).
// Post-deneb:
// [IGNORE] `aggregate.data.slot` is equal to or earlier than the `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
// -- i.e. `aggregate.data.slot <= current_slot`
// (a client MAY queue future aggregates for processing at the appropriate slot).
// [IGNORE] the epoch of `aggregate.data.slot` is either the current or previous epoch
// (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
// -- i.e. `compute_epoch_at_slot(aggregate.data.slot) in (get_previous_epoch(state), get_current_epoch(state))`
verifyPropagationSlotRange(fork, chain, attSlot);
}

View File

@@ -320,9 +320,17 @@ async function validateAttestationNoSignatureCheck(
});
}
// Pre-deneb:
// [IGNORE] attestation.data.slot is within the last ATTESTATION_PROPAGATION_SLOT_RANGE slots (within a MAXIMUM_GOSSIP_CLOCK_DISPARITY allowance)
// -- i.e. attestation.data.slot + ATTESTATION_PROPAGATION_SLOT_RANGE >= current_slot >= attestation.data.slot
// (a client MAY queue future attestations for processing at the appropriate slot).
// Post-deneb:
// [IGNORE] `attestation.data.slot` is equal to or earlier than the `current_slot` (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
// -- i.e. `attestation.data.slot <= current_slot`
// (a client MAY queue future attestation for processing at the appropriate slot).
// [IGNORE] the epoch of `attestation.data.slot` is either the current or previous epoch
// (with a `MAXIMUM_GOSSIP_CLOCK_DISPARITY` allowance)
// -- i.e. `compute_epoch_at_slot(attestation.data.slot) in (get_previous_epoch(state), get_current_epoch(state))`
verifyPropagationSlotRange(fork, chain, attestationOrCache.attestation.data.slot);
}

View File

@@ -5,7 +5,11 @@ import {
KZG_COMMITMENT_SUBTREE_INDEX0,
isForkPostElectra,
} from "@lodestar/params";
import {computeStartSlotAtEpoch, getBlockHeaderProposerSignatureSet} from "@lodestar/state-transition";
import {
computeEpochAtSlot,
computeStartSlotAtEpoch,
getBlockHeaderProposerSignatureSet,
} from "@lodestar/state-transition";
import {BlobIndex, Root, Slot, SubnetID, deneb, ssz} from "@lodestar/types";
import {toRootHex, verifyMerkleBranch} from "@lodestar/utils";
@@ -25,7 +29,7 @@ export async function validateGossipBlobSidecar(
const blobSlot = blobSidecar.signedBlockHeader.message.slot;
// [REJECT] The sidecar's index is consistent with `MAX_BLOBS_PER_BLOCK` -- i.e. `blob_sidecar.index < MAX_BLOBS_PER_BLOCK`.
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork);
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(blobSlot));
if (blobSidecar.index >= maxBlobsPerBlock) {
throw new BlobSidecarGossipError(GossipAction.REJECT, {
code: BlobSidecarErrorCode.INDEX_TOO_LARGE,

View File

@@ -1,6 +1,7 @@
import {ChainForkConfig} from "@lodestar/config";
import {ForkName, isForkPostDeneb} from "@lodestar/params";
import {
computeEpochAtSlot,
computeStartSlotAtEpoch,
computeTimeAtSlot,
getBlockProposerSignatureSet,
@@ -113,7 +114,7 @@ export async function validateGossipBlock(
// [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK
if (isForkPostDeneb(fork)) {
const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length;
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(fork);
const maxBlobsPerBlock = chain.config.getMaxBlobsPerBlock(computeEpochAtSlot(blockSlot));
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
throw new BlockGossipError(GossipAction.REJECT, {
code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS,

View File

@@ -1,6 +1,7 @@
import {PeerScoreStatsDump} from "@chainsafe/libp2p-gossipsub/score";
import {PublishOpts} from "@chainsafe/libp2p-gossipsub/types";
import {routes} from "@lodestar/api";
import {SpecJson} from "@lodestar/config";
import {LoggerNodeOpts} from "@lodestar/logger/node";
import {ResponseIncoming} from "@lodestar/reqresp";
import {phase0} from "@lodestar/types";
@@ -75,7 +76,7 @@ export interface INetworkCore extends INetworkCorePublic {
export type NetworkWorkerData = {
// TODO: Review if NetworkOptions is safe for passing
opts: NetworkOptions;
chainConfigJson: Record<string, string>;
chainConfigJson: SpecJson;
genesisValidatorsRoot: Uint8Array;
genesisTime: number;
activeValidatorCount: number;

View File

@@ -7,7 +7,7 @@ import {BeaconConfig} from "@lodestar/config";
import {LoggerNode} from "@lodestar/logger/node";
import {ForkSeq, NUMBER_OF_COLUMNS} from "@lodestar/params";
import {ResponseIncoming} from "@lodestar/reqresp";
import {computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition";
import {computeEpochAtSlot, computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition";
import {
AttesterSlashing,
ColumnIndex,
@@ -550,11 +550,11 @@ export class Network implements INetwork {
peerId: PeerIdStr,
request: deneb.BlobSidecarsByRangeRequest
): Promise<deneb.BlobSidecar[]> {
const fork = this.config.getForkName(request.startSlot);
const epoch = computeEpochAtSlot(request.startSlot);
return collectMaxResponseTyped(
this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRange, [Version.V1], request),
// request's count represent the slots, so the actual max count received could be slots * blobs per slot
request.count * this.config.getMaxBlobsPerBlock(fork),
request.count * this.config.getMaxBlobsPerBlock(epoch),
responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRange]
);
}

View File

@@ -112,10 +112,6 @@ export enum ReprocessRejectReason {
* Cannot accept work reason for metrics
*/
export enum CannotAcceptWorkReason {
/**
* Validating or processing gossip block at current slot.
*/
processingCurrentSlotBlock = "processing_current_slot_block",
/**
* bls is busy.
*/
@@ -160,7 +156,6 @@ export class NetworkProcessor {
// to be stored in this Map and reprocessed once the block comes
private readonly awaitingGossipsubMessagesByRootBySlot: MapDef<Slot, MapDef<RootHex, Set<PendingGossipsubMessage>>>;
private unknownBlockGossipsubMessagesCount = 0;
private isProcessingCurrentSlotBlock = false;
private unknownRootsBySlot = new MapDef<Slot, Set<RootHex>>(() => new Set());
constructor(
@@ -269,15 +264,6 @@ export class NetworkProcessor {
});
return;
}
if (
slot === clockSlot &&
(topicType === GossipType.beacon_block ||
topicType === GossipType.blob_sidecar ||
topicType === GossipType.data_column_sidecar)
) {
// in the worse case if the current slot block is not valid, this will be reset in the next slot
this.isProcessingCurrentSlotBlock = true;
}
message.msgSlot = slot;
// check if we processed a block with this root
// no need to check if root is a descendant of the current finalized block, it will be checked once we validate the message if needed
@@ -324,7 +310,6 @@ export class NetworkProcessor {
block: string;
executionOptimistic: boolean;
}): Promise<void> {
this.isProcessingCurrentSlotBlock = false;
const byRootGossipsubMessages = this.awaitingGossipsubMessagesByRootBySlot.getOrDefault(slot);
const waitingGossipsubMessages = byRootGossipsubMessages.getOrDefault(rootHex);
if (waitingGossipsubMessages.size === 0) {
@@ -351,7 +336,6 @@ export class NetworkProcessor {
}
private onClockSlot(clockSlot: Slot): void {
this.isProcessingCurrentSlotBlock = false;
const nowSec = Date.now() / 1000;
for (const [slot, gossipMessagesByRoot] of this.awaitingGossipsubMessagesByRootBySlot.entries()) {
if (slot < clockSlot) {
@@ -498,10 +482,6 @@ export class NetworkProcessor {
* Return null if chain can accept work, otherwise return the reason why it cannot accept work
*/
private checkAcceptWork(): null | CannotAcceptWorkReason {
if (this.isProcessingCurrentSlotBlock) {
return CannotAcceptWorkReason.processingCurrentSlotBlock;
}
if (!this.chain.blsThreadPoolCanAcceptWork()) {
return CannotAcceptWorkReason.bls;
}

View File

@@ -1,17 +1,18 @@
import {Bytes32} from "@lodestar/types";
import {GRAFFITI_SIZE} from "../constants/index.js";
import {ClientVersion} from "../execution/index.js";
/**
* Parses a graffiti UTF8 string and returns a 32 bytes buffer right padded with zeros
*/
export function toGraffitiBuffer(graffiti: string): Buffer {
export function toGraffitiBytes(graffiti: string): Bytes32 {
return Buffer.concat([Buffer.from(graffiti, "utf8"), Buffer.alloc(GRAFFITI_SIZE, 0)], GRAFFITI_SIZE);
}
/**
* Converts a graffiti from 32 bytes buffer back to a UTF-8 string
*/
export function fromGraffitiBuffer(graffiti: Uint8Array): string {
export function fromGraffitiBytes(graffiti: Bytes32): string {
return Buffer.from(graffiti.buffer, graffiti.byteOffset, graffiti.byteLength)
.toString("utf8")
.replaceAll("\u0000", "");

View File

@@ -55,6 +55,7 @@ vi.mock("@lodestar/fork-choice", async (importActual) => {
getBlock: vi.fn(),
getAllAncestorBlocks: vi.fn(),
getAllNonAncestorBlocks: vi.fn(),
getAllAncestorAndNonAncestorBlocks: vi.fn(),
iterateAncestorBlocks: vi.fn(),
getBlockSummariesByParentRoot: vi.fn(),
getCanonicalBlockAtSlot: vi.fn(),

View File

@@ -13,7 +13,7 @@ import {CommonBlockBody} from "../../../../../src/chain/interface.js";
import {BlockType, produceBlockBody} from "../../../../../src/chain/produceBlock/index.js";
import {PayloadIdCache} from "../../../../../src/execution/index.js";
import {SyncState} from "../../../../../src/sync/interface.js";
import {toGraffitiBuffer} from "../../../../../src/util/graffiti.js";
import {toGraffitiBytes} from "../../../../../src/util/graffiti.js";
import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js";
import {generateCachedBellatrixState, zeroProtoBlock} from "../../../../utils/state.js";
import {generateProtoBlock} from "../../../../utils/typeGenerator.js";
@@ -200,7 +200,7 @@ describe("api/validator - produceBlockV3", () => {
await api.produceBlockV3({slot, randaoReveal, graffiti, feeRecipient});
expect(modules.chain.produceBlock).toBeCalledWith({
randaoReveal,
graffiti: toGraffitiBuffer(graffiti),
graffiti: toGraffitiBytes(graffiti),
slot,
parentBlockRoot,
feeRecipient,
@@ -211,7 +211,7 @@ describe("api/validator - produceBlockV3", () => {
await api.produceBlockV3({slot, randaoReveal, graffiti});
expect(modules.chain.produceBlock).toBeCalledWith({
randaoReveal,
graffiti: toGraffitiBuffer(graffiti),
graffiti: toGraffitiBytes(graffiti),
slot,
parentBlockRoot,
feeRecipient: undefined,
@@ -253,7 +253,7 @@ describe("api/validator - produceBlockV3", () => {
// use fee recipient passed in produceBlockBody call for payload gen in engine notifyForkchoiceUpdate
await produceBlockBody.call(modules.chain as unknown as BeaconChain, BlockType.Full, state, {
randaoReveal,
graffiti: toGraffitiBuffer(graffiti),
graffiti: toGraffitiBytes(graffiti),
slot,
feeRecipient,
parentSlot: slot - 1,
@@ -278,7 +278,7 @@ describe("api/validator - produceBlockV3", () => {
modules.chain["beaconProposerCache"].getOrDefault.mockReturnValue("0x fee recipient address");
await produceBlockBody.call(modules.chain as unknown as BeaconChain, BlockType.Full, state, {
randaoReveal,
graffiti: toGraffitiBuffer(graffiti),
graffiti: toGraffitiBytes(graffiti),
slot,
parentSlot: slot - 1,
parentBlockRoot: fromHexString(ZERO_HASH_HEX),

View File

@@ -50,8 +50,10 @@ describe("block archiver task", () => {
const canonicalBlocks = [blocks[4], blocks[3], blocks[1], blocks[0]];
const nonCanonicalBlocks = [blocks[2]];
const currentEpoch = 8;
vi.spyOn(forkChoiceStub, "getAllAncestorBlocks").mockReturnValue(canonicalBlocks);
vi.spyOn(forkChoiceStub, "getAllNonAncestorBlocks").mockReturnValue(nonCanonicalBlocks);
vi.spyOn(forkChoiceStub, "getAllAncestorAndNonAncestorBlocks").mockReturnValue({
ancestors: canonicalBlocks,
nonAncestors: nonCanonicalBlocks,
});
await archiveBlocks(
config,
dbStub,

View File

@@ -27,7 +27,10 @@ describe("gossip block validation", () => {
beforeEach(() => {
// Fill up with kzg commitments
block.body.blobKzgCommitments = Array.from({length: config.MAX_BLOBS_PER_BLOCK}, () => new Uint8Array([0]));
block.body.blobKzgCommitments = Array.from(
{length: config.BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK},
() => new Uint8Array([0])
);
chain = getMockedBeaconChain();
vi.spyOn(chain.clock, "currentSlotWithGossipDisparity", "get").mockReturnValue(clockSlot);

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {ClientCode} from "../../../src/execution/index.js";
import {getDefaultGraffiti, toGraffitiBuffer} from "../../../src/util/graffiti.js";
import {getDefaultGraffiti, toGraffitiBytes} from "../../../src/util/graffiti.js";
describe("Graffiti helper", () => {
describe("toGraffitiBuffer", () => {
@@ -23,7 +23,7 @@ describe("Graffiti helper", () => {
];
for (const {input, result} of cases) {
it(`Convert graffiti UTF8 ${input} to Buffer`, () => {
expect(toGraffitiBuffer(input).toString("hex")).toBe(result);
expect(Buffer.from(toGraffitiBytes(input)).toString("hex")).toBe(result);
});
}
});

View File

@@ -29,6 +29,7 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: forkEpoch,
BLOB_SCHEDULE: [{EPOCH: forkEpoch, MAX_BLOBS_PER_BLOCK: 6}],
});
case ForkName.electra:
return createChainForkConfig({
@@ -37,6 +38,10 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: 0,
ELECTRA_FORK_EPOCH: forkEpoch,
BLOB_SCHEDULE: [
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 6},
{EPOCH: forkEpoch, MAX_BLOBS_PER_BLOCK: 9},
],
});
case ForkName.fulu:
return createChainForkConfig({
@@ -46,6 +51,10 @@ export function getConfig(fork: ForkName, forkEpoch = 0): ChainForkConfig {
DENEB_FORK_EPOCH: 0,
ELECTRA_FORK_EPOCH: 0,
FULU_FORK_EPOCH: forkEpoch,
BLOB_SCHEDULE: [
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 6},
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 9},
],
});
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@chainsafe/lodestar",
"version": "1.29.0",
"version": "1.30.0",
"description": "Command line interface for lodestar",
"author": "ChainSafe Systems",
"license": "LGPL-3.0",
@@ -62,17 +62,17 @@
"@libp2p/crypto": "^5.0.15",
"@libp2p/interface": "^2.7.0",
"@libp2p/peer-id": "^5.1.0",
"@lodestar/api": "^1.29.0",
"@lodestar/beacon-node": "^1.29.0",
"@lodestar/config": "^1.29.0",
"@lodestar/db": "^1.29.0",
"@lodestar/light-client": "^1.29.0",
"@lodestar/logger": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/state-transition": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/validator": "^1.29.0",
"@lodestar/api": "^1.30.0",
"@lodestar/beacon-node": "^1.30.0",
"@lodestar/config": "^1.30.0",
"@lodestar/db": "^1.30.0",
"@lodestar/light-client": "^1.30.0",
"@lodestar/logger": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/state-transition": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"@lodestar/validator": "^1.30.0",
"@multiformats/multiaddr": "^12.1.3",
"deepmerge": "^4.3.1",
"ethers": "^6.7.0",
@@ -87,7 +87,7 @@
"yargs": "^17.7.1"
},
"devDependencies": {
"@lodestar/test-utils": "^1.29.0",
"@lodestar/test-utils": "^1.30.0",
"@types/debug": "^4.1.7",
"@types/inquirer": "^9.0.3",
"@types/proper-lockfile": "^4.1.4",

View File

@@ -1,6 +1,6 @@
{
"name": "@lodestar/config",
"version": "1.29.0",
"version": "1.30.0",
"description": "Chain configuration required for lodestar",
"author": "ChainSafe Systems",
"license": "Apache-2.0",
@@ -65,8 +65,8 @@
],
"dependencies": {
"@chainsafe/ssz": "^1.2.0",
"@lodestar/params": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0"
"@lodestar/params": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0"
}
}

View File

@@ -109,7 +109,7 @@ export const chainConfig: ChainConfig = {
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
BLOB_SIDECAR_SUBNET_COUNT: 6,
MAX_BLOBS_PER_BLOCK: 6,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS: 768,
// Electra
@@ -119,7 +119,7 @@ export const chainConfig: ChainConfig = {
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 128000000000,
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9,
MAX_BLOBS_PER_BLOCK_ELECTRA: 9,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152,
// Fulu
@@ -130,5 +130,13 @@ export const chainConfig: ChainConfig = {
NODE_CUSTODY_REQUIREMENT: 1,
VALIDATOR_CUSTODY_REQUIREMENT: 8,
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000,
MAX_BLOBS_PER_BLOCK_FULU: 12,
// Blob Scheduling
// ---------------------------------------------------------------
BLOB_SCHEDULE: [
// Deneb
{EPOCH: 269568, MAX_BLOBS_PER_BLOCK: 6},
// Electra
{EPOCH: 364032, MAX_BLOBS_PER_BLOCK: 9},
],
};

View File

@@ -105,7 +105,7 @@ export const chainConfig: ChainConfig = {
MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096,
BLOB_SIDECAR_SUBNET_COUNT: 6,
MAX_BLOBS_PER_BLOCK: 6,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[0].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS: 768,
// Electra
@@ -115,7 +115,7 @@ export const chainConfig: ChainConfig = {
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA: 64000000000,
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 9,
MAX_BLOBS_PER_BLOCK_ELECTRA: 9,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 1152,
// Fulu
@@ -126,5 +126,13 @@ export const chainConfig: ChainConfig = {
NODE_CUSTODY_REQUIREMENT: 1,
VALIDATOR_CUSTODY_REQUIREMENT: 8,
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: 32000000000,
MAX_BLOBS_PER_BLOCK_FULU: 12,
// Blob Scheduling
// ---------------------------------------------------------------
BLOB_SCHEDULE: [
// Deneb
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 6},
// Electra
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 9},
],
};

View File

@@ -1,10 +1,18 @@
import {fromHex, toHex} from "@lodestar/utils";
import {ChainConfig, SpecValue, SpecValueTypeName, chainConfigTypes} from "./types.js";
import {
BlobScheduleEntry,
ChainConfig,
SpecJson,
SpecValue,
SpecValueTypeName,
chainConfigTypes,
isBlobSchedule,
} from "./types.js";
const MAX_UINT64_JSON = "18446744073709551615";
export function chainConfigToJson(config: ChainConfig): Record<string, string> {
const json: Record<string, string> = {};
export function chainConfigToJson(config: ChainConfig): SpecJson {
const json: SpecJson = {};
for (const key of Object.keys(chainConfigTypes) as (keyof ChainConfig)[]) {
const value = config[key];
@@ -29,8 +37,8 @@ export function chainConfigFromJson(json: Record<string, unknown>): ChainConfig
return config;
}
export function specValuesToJson(spec: Record<string, SpecValue>): Record<string, string> {
const json: Record<string, string> = {};
export function specValuesToJson(spec: Record<string, SpecValue>): SpecJson {
const json: SpecJson = {};
for (const key of Object.keys(spec)) {
json[key] = serializeSpecValue(spec[key], toSpecValueTypeName(spec[key]));
@@ -45,10 +53,14 @@ export function toSpecValueTypeName(value: SpecValue): SpecValueTypeName {
if (typeof value === "number") return "number";
if (typeof value === "bigint") return "bigint";
if (typeof value === "string") return "string";
if (isBlobSchedule(value)) return "blob_schedule";
throw Error(`Unknown value type ${value}`);
}
export function serializeSpecValue(value: SpecValue, typeName: SpecValueTypeName): string {
export function serializeSpecValue(
value: SpecValue,
typeName: SpecValueTypeName
): string | Record<keyof BlobScheduleEntry, string>[] {
switch (typeName) {
case "number":
if (typeof value !== "number") {
@@ -76,12 +88,64 @@ export function serializeSpecValue(value: SpecValue, typeName: SpecValueTypeName
throw Error(`Invalid value ${value.toString()} expected string`);
}
return value;
case "blob_schedule":
if (!isBlobSchedule(value)) {
throw Error(`Invalid value ${value.toString()} expected BlobSchedule`);
}
return value.map(({EPOCH, MAX_BLOBS_PER_BLOCK}) => ({
EPOCH: EPOCH === Infinity ? MAX_UINT64_JSON : EPOCH.toString(10),
MAX_BLOBS_PER_BLOCK: MAX_BLOBS_PER_BLOCK === Infinity ? MAX_UINT64_JSON : MAX_BLOBS_PER_BLOCK.toString(10),
}));
}
}
export function deserializeSpecValue(valueStr: unknown, typeName: SpecValueTypeName, keyName: string): SpecValue {
if (typeName === "blob_schedule") {
if (!Array.isArray(valueStr)) {
throw Error(`Invalid BLOB_SCHEDULE value ${valueStr} expected array`);
}
const blobSchedule = valueStr.map((entry, i) => {
if (typeof entry !== "object" || entry === null) {
throw Error(`Invalid BLOB_SCHEDULE[${i}] entry ${entry} expected object`);
}
const out = {} as BlobScheduleEntry;
for (const key of ["EPOCH", "MAX_BLOBS_PER_BLOCK"] as Array<keyof BlobScheduleEntry>) {
const value = entry[key];
if (value === undefined) {
throw Error(`Invalid BLOB_SCHEDULE[${i}] entry ${JSON.stringify(entry)} missing ${key}`);
}
if (typeof value !== "string") {
throw Error(`Invalid BLOB_SCHEDULE[${i}].${key} value ${value} expected string`);
}
if (value === MAX_UINT64_JSON) {
out[key] = Infinity;
} else {
const parsed = parseInt(value, 10);
if (Number.isNaN(parsed)) {
throw Error(`Invalid BLOB_SCHEDULE[${i}].${key} value ${value} expected number`);
}
out[key] = parsed;
}
}
return out;
});
return blobSchedule;
}
if (typeof valueStr !== "string") {
throw Error(`Invalid ${keyName} value ${valueStr as string} expected string`);
throw Error(`Invalid ${keyName} value ${valueStr} expected string`);
}
switch (typeName) {

View File

@@ -66,6 +66,14 @@ export const gnosisChainConfig: ChainConfig = {
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: 64000000000,
BLOB_SIDECAR_SUBNET_COUNT_ELECTRA: 2,
MAX_BLOBS_PER_BLOCK_ELECTRA: 2,
// MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA
// MAX_REQUEST_BLOCKS_DENEB * BLOB_SCHEDULE[1].MAX_BLOBS_PER_BLOCK
MAX_REQUEST_BLOB_SIDECARS_ELECTRA: 256,
// Blob Scheduling
BLOB_SCHEDULE: [
// Deneb
{EPOCH: 889856, MAX_BLOBS_PER_BLOCK: 2},
// Electra
{EPOCH: 1337856, MAX_BLOBS_PER_BLOCK: 2},
],
};

View File

@@ -89,7 +89,9 @@ export type ChainConfig = {
NODE_CUSTODY_REQUIREMENT: number;
VALIDATOR_CUSTODY_REQUIREMENT: number;
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: number;
MAX_BLOBS_PER_BLOCK_FULU: number;
// Blob Scheduling
BLOB_SCHEDULE: BlobSchedule;
};
export const chainConfigTypes: SpecTypes<ChainConfig> = {
@@ -171,11 +173,33 @@ export const chainConfigTypes: SpecTypes<ChainConfig> = {
NODE_CUSTODY_REQUIREMENT: "number",
VALIDATOR_CUSTODY_REQUIREMENT: "number",
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: "number",
MAX_BLOBS_PER_BLOCK_FULU: "number",
// Blob Scheduling
BLOB_SCHEDULE: "blob_schedule",
};
export type BlobScheduleEntry = {
EPOCH: number;
MAX_BLOBS_PER_BLOCK: number;
};
export type BlobSchedule = BlobScheduleEntry[];
export function isBlobSchedule(value: unknown): value is BlobSchedule {
return (
Array.isArray(value) &&
value.every(
(entry) =>
typeof entry === "object" &&
entry !== null &&
typeof entry.EPOCH === "number" &&
typeof entry.MAX_BLOBS_PER_BLOCK === "number"
)
);
}
/** Allows values in a Spec file */
export type SpecValue = number | bigint | Uint8Array | string;
export type SpecValue = number | bigint | Uint8Array | string | BlobSchedule;
/** Type value name of each spec field. Numbers are ignored since they are the most common */
export type SpecValueType<V extends SpecValue> = V extends number
@@ -186,7 +210,9 @@ export type SpecValueType<V extends SpecValue> = V extends number
? "bytes"
: V extends string
? "string"
: never;
: V extends BlobSchedule
? "blob_schedule"
: never;
/** All possible type names for a SpecValue */
export type SpecValueTypeName = SpecValueType<SpecValue>;
@@ -194,3 +220,5 @@ export type SpecValueTypeName = SpecValueType<SpecValue>;
export type SpecTypes<Spec extends Record<string, SpecValue>> = {
[K in keyof Spec]: SpecValueType<Spec[K]>;
};
export type SpecJson = Record<string, string | Record<string, string>[]>;

View File

@@ -138,15 +138,37 @@ export function createForkConfig(config: ChainConfig): ForkConfig {
}
return sszTypesFor(forkName);
},
getMaxBlobsPerBlock(fork: ForkName): number {
getMaxBlobsPerBlock(epoch: Epoch): number {
// TODO Fulu: Max blobs of Deneb and Electra are hardcoded for fusaka devnet-0. Remove this for devnet-1
const fork = this.getForkInfoAtEpoch(epoch).name;
switch (fork) {
case ForkName.fulu:
return config.MAX_BLOBS_PER_BLOCK_FULU;
case ForkName.electra:
return config.MAX_BLOBS_PER_BLOCK_ELECTRA;
default:
case ForkName.deneb:
return config.MAX_BLOBS_PER_BLOCK;
}
if (config.BLOB_SCHEDULE.length === 0) {
throw Error("Attempt to get MAX_BLOBS_PER_BLOCK from empty BLOB_SCHEDULE");
}
// Sort by epoch in descending order to find the latest applicable value
const blobSchedule = [...config.BLOB_SCHEDULE].sort((a, b) => {
if (a.EPOCH !== b.EPOCH) {
return b.EPOCH - a.EPOCH;
}
return b.MAX_BLOBS_PER_BLOCK - a.MAX_BLOBS_PER_BLOCK;
});
for (const entry of blobSchedule) {
if (epoch >= entry.EPOCH) {
return entry.MAX_BLOBS_PER_BLOCK;
}
}
// Only for testing. Should never reach this line on a public network.
return Math.min(...blobSchedule.map((e) => e.MAX_BLOBS_PER_BLOCK));
},
getMaxRequestBlobSidecars(fork: ForkName): number {
return isForkPostElectra(fork) ? config.MAX_REQUEST_BLOB_SIDECARS_ELECTRA : config.MAX_REQUEST_BLOB_SIDECARS;

View File

@@ -39,8 +39,8 @@ export type ForkConfig = {
getPostBellatrixForkTypes(slot: Slot): SSZTypesFor<ForkPostBellatrix>;
/** Get post-deneb SSZ types by hard-fork*/
getPostDenebForkTypes(slot: Slot): SSZTypesFor<ForkPostDeneb>;
/** Get max blobs per block by hard-fork */
getMaxBlobsPerBlock(fork: ForkName): number;
/** Get max blobs per block at a given epoch */
getMaxBlobsPerBlock(epoch: Epoch): number;
/** Get max request blob sidecars by hard-fork */
getMaxRequestBlobSidecars(fork: ForkName): number;
};

View File

@@ -0,0 +1,86 @@
import {beforeAll, describe, expect, it} from "vitest";
import {chainConfig} from "../../src/default.js";
import {ChainConfig, createForkConfig} from "../../src/index.js";
describe("getMaxBlobsPerBlock", () => {
let defaultConfig: ChainConfig;
beforeAll(() => {
// Force tests to run on fulu fork
// TODO Fulu: getMaxBlobsPerBlock's result is hardcoded for deneb and electra in fusaka devnet-0. So we need to define
// defaultConfig to force tests to run on fulu to get expected result. Remove this after devnet-0.
defaultConfig = {
...chainConfig,
ALTAIR_FORK_EPOCH: 0,
BELLATRIX_FORK_EPOCH: 0,
CAPELLA_FORK_EPOCH: 0,
DENEB_FORK_EPOCH: 0,
ELECTRA_FORK_EPOCH: 0,
FULU_FORK_EPOCH: 0,
};
});
it("should throw an error if BLOB_SCHEDULE is empty", () => {
const config = createForkConfig({...defaultConfig, BLOB_SCHEDULE: []});
expect(() => config.getMaxBlobsPerBlock(0)).toThrowError();
});
it("should return same value for passed epochs if there is only a single BLOB_SCHEDULE entry", () => {
const config = createForkConfig({
...defaultConfig,
BLOB_SCHEDULE: [{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 10}],
});
expect(config.getMaxBlobsPerBlock(0)).toEqual(10);
expect(config.getMaxBlobsPerBlock(5)).toEqual(10);
expect(config.getMaxBlobsPerBlock(10)).toEqual(10);
});
it("should select correct value for passed epoch based on BLOB_SCHEDULE thresholds", () => {
const config = createForkConfig({
...defaultConfig,
BLOB_SCHEDULE: [
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 1},
{EPOCH: 2, MAX_BLOBS_PER_BLOCK: 2},
{EPOCH: 4, MAX_BLOBS_PER_BLOCK: 3},
],
});
expect(config.getMaxBlobsPerBlock(0)).toEqual(1);
expect(config.getMaxBlobsPerBlock(1)).toEqual(1);
expect(config.getMaxBlobsPerBlock(2)).toEqual(2);
expect(config.getMaxBlobsPerBlock(3)).toEqual(2);
expect(config.getMaxBlobsPerBlock(4)).toEqual(3);
expect(config.getMaxBlobsPerBlock(100)).toEqual(3);
});
it("should return correct values if BLOB_SCHEDULE entries are unsorted", () => {
const config = createForkConfig({
...defaultConfig,
BLOB_SCHEDULE: [
{EPOCH: 15, MAX_BLOBS_PER_BLOCK: 1},
{EPOCH: 5, MAX_BLOBS_PER_BLOCK: 3},
{EPOCH: 10, MAX_BLOBS_PER_BLOCK: 5},
],
});
expect(config.getMaxBlobsPerBlock(0)).toEqual(1);
expect(config.getMaxBlobsPerBlock(6)).toEqual(3);
expect(config.getMaxBlobsPerBlock(10)).toEqual(5);
expect(config.getMaxBlobsPerBlock(14)).toEqual(5);
expect(config.getMaxBlobsPerBlock(15)).toEqual(1);
expect(config.getMaxBlobsPerBlock(16)).toEqual(1);
});
it("should return minimum value if epoch is below lowest configured BLOB_SCHEDULE epoch", () => {
const config = createForkConfig({
...defaultConfig,
BLOB_SCHEDULE: [
{EPOCH: 5, MAX_BLOBS_PER_BLOCK: 3},
{EPOCH: 10, MAX_BLOBS_PER_BLOCK: 5},
{EPOCH: 15, MAX_BLOBS_PER_BLOCK: 2},
],
});
expect(config.getMaxBlobsPerBlock(0)).toEqual(2);
});
});

View File

@@ -1,6 +1,6 @@
import {describe, expect, it} from "vitest";
import {chainConfig} from "../../src/default.js";
import {chainConfigFromJson, chainConfigToJson} from "../../src/index.js";
import {BlobSchedule, chainConfigFromJson, chainConfigToJson} from "../../src/index.js";
describe("chainConfig JSON", () => {
it("Convert to and from JSON", () => {
@@ -9,4 +9,19 @@ describe("chainConfig JSON", () => {
expect(chainConfigRes).toEqual(chainConfig);
});
it("Custom blob schedule", () => {
const blobSchedule: BlobSchedule = [
{EPOCH: 0, MAX_BLOBS_PER_BLOCK: 10},
{EPOCH: 10, MAX_BLOBS_PER_BLOCK: Infinity},
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: 20},
{EPOCH: Infinity, MAX_BLOBS_PER_BLOCK: Infinity},
];
const configWithCustomBlobSchedule = {...chainConfig, BLOB_SCHEDULE: blobSchedule};
const json = chainConfigToJson(configWithCustomBlobSchedule);
const chainConfigRes = chainConfigFromJson(json);
expect(chainConfigRes).toEqual(configWithCustomBlobSchedule);
});
});

View File

@@ -1,6 +1,6 @@
{
"name": "@lodestar/db",
"version": "1.29.0",
"version": "1.30.0",
"description": "DB modules of Lodestar",
"author": "ChainSafe Systems",
"homepage": "https://github.com/ChainSafe/lodestar#readme",
@@ -36,12 +36,12 @@
},
"dependencies": {
"@chainsafe/ssz": "^1.2.0",
"@lodestar/config": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/config": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"classic-level": "^1.4.1",
"it-all": "^3.0.4"
},
"devDependencies": {
"@lodestar/logger": "^1.29.0"
"@lodestar/logger": "^1.30.0"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@lodestar/flare",
"version": "1.29.0",
"version": "1.30.0",
"description": "Beacon chain debugging tool",
"author": "ChainSafe Systems",
"license": "Apache-2.0",
@@ -60,12 +60,12 @@
"dependencies": {
"@chainsafe/bls-keygen": "^0.4.0",
"@chainsafe/blst": "^2.2.0",
"@lodestar/api": "^1.29.0",
"@lodestar/config": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/state-transition": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/api": "^1.30.0",
"@lodestar/config": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/state-transition": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"source-map-support": "^0.5.21",
"yargs": "^17.7.1"
},

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": "./lib/index.js",
"types": "./lib/index.d.ts",
@@ -37,11 +37,11 @@
},
"dependencies": {
"@chainsafe/ssz": "^1.2.0",
"@lodestar/config": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/state-transition": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0"
"@lodestar/config": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/state-transition": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0"
},
"keywords": [
"ethereum",

View File

@@ -943,6 +943,19 @@ export class ForkChoice implements IForkChoice {
return this.protoArray.getAllNonAncestorNodes(blockRoot);
}
/**
* Returns both ancestor and non-ancestor blocks in a single traversal.
*/
getAllAncestorAndNonAncestorBlocks(blockRoot: RootHex): {ancestors: ProtoBlock[]; nonAncestors: ProtoBlock[]} {
const {ancestors, nonAncestors} = this.protoArray.getAllAncestorAndNonAncestorNodes(blockRoot);
return {
// the last node is the previous finalized one, it's there to check onBlock finalized checkpoint only.
ancestors: ancestors.slice(0, ancestors.length - 1),
nonAncestors,
};
}
getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null {
if (slot > this.head.slot) {
return null;

View File

@@ -217,6 +217,10 @@ export interface IForkChoice {
* The same to iterateAncestorBlocks but this gets non-ancestor nodes instead of ancestor nodes.
*/
getAllNonAncestorBlocks(blockRoot: RootHex): ProtoBlock[];
/**
* Returns both ancestor and non-ancestor blocks in a single traversal.
*/
getAllAncestorAndNonAncestorBlocks(blockRoot: RootHex): {ancestors: ProtoBlock[]; nonAncestors: ProtoBlock[]};
getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null;
getCanonicalBlockClosestLteSlot(slot: Slot): ProtoBlock | null;
/**

View File

@@ -903,6 +903,44 @@ export class ProtoArray {
return result;
}
/**
* Returns both ancestor and non-ancestor nodes in a single traversal.
*/
getAllAncestorAndNonAncestorNodes(blockRoot: RootHex): {ancestors: ProtoNode[]; nonAncestors: ProtoNode[]} {
const startIndex = this.indices.get(blockRoot);
if (startIndex === undefined) {
return {ancestors: [], nonAncestors: []};
}
let node = this.nodes[startIndex];
if (node === undefined) {
throw new ProtoArrayError({
code: ProtoArrayErrorCode.INVALID_NODE_INDEX,
index: startIndex,
});
}
const ancestors: ProtoNode[] = [];
const nonAncestors: ProtoNode[] = [];
let nodeIndex = startIndex;
while (node.parent !== undefined) {
ancestors.push(node);
const parentIndex = node.parent;
node = this.getNodeFromIndex(parentIndex);
// Nodes between nodeIndex and parentIndex are non-ancestor nodes
nonAncestors.push(...this.getNodesBetween(nodeIndex, parentIndex));
nodeIndex = parentIndex;
}
ancestors.push(node);
nonAncestors.push(...this.getNodesBetween(nodeIndex, 0));
return {ancestors, nonAncestors};
}
hasBlock(blockRoot: RootHex): boolean {
return this.indices.has(blockRoot);
}

View File

@@ -126,6 +126,38 @@ describe("Forkchoice", () => {
expect(summaries[0]).toEqual({...block, bestChild: undefined, bestDescendant: undefined, parent: 0, weight: 0});
});
it("getAllAncestorAndNonAncestorBlocks equals getAllAncestorBlocks + getAllNonAncestorBlocks", () => {
// Create a simple chain: 0 -> 1 -> 2 -> 3
populateProtoArray(genesisSlot + 3);
// Create a fork by adding block 10 with parent at genesis
const forkBlock = {
...getBlock(genesisSlot + 10),
parentRoot: finalizedRoot, // Connect directly to genesis
};
protoArr.onBlock(forkBlock, forkBlock.slot);
const forkchoice = new ForkChoice(config, fcStore, protoArr);
// Test with a block from the canonical chain
const canonicalBlockRoot = getBlockRoot(genesisSlot + 3);
const canonicalAncestorBlocks = forkchoice.getAllAncestorBlocks(canonicalBlockRoot);
const canonicalNonAncestorBlocks = forkchoice.getAllNonAncestorBlocks(canonicalBlockRoot);
const canonicalCombined = forkchoice.getAllAncestorAndNonAncestorBlocks(canonicalBlockRoot);
expect(canonicalCombined.ancestors).toEqual(canonicalAncestorBlocks);
expect(canonicalCombined.nonAncestors).toEqual(canonicalNonAncestorBlocks);
// Test with a block from the fork chain
const forkBlockRoot = getBlockRoot(genesisSlot + 10);
const forkAncestorBlocks = forkchoice.getAllAncestorBlocks(forkBlockRoot);
const forkNonAncestorBlocks = forkchoice.getAllNonAncestorBlocks(forkBlockRoot);
const forkCombined = forkchoice.getAllAncestorAndNonAncestorBlocks(forkBlockRoot);
expect(forkCombined.ancestors).toEqual(forkAncestorBlocks);
expect(forkCombined.nonAncestors).toEqual(forkNonAncestorBlocks);
});
beforeAll(() => {
expect(SLOTS_PER_EPOCH).toBe(32);
});

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -74,11 +74,11 @@
"@chainsafe/blst": "^0.2.0",
"@chainsafe/persistent-merkle-tree": "^1.1.0",
"@chainsafe/ssz": "^1.2.0",
"@lodestar/api": "^1.29.0",
"@lodestar/config": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/api": "^1.30.0",
"@lodestar/config": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"mitt": "^3.0.0"
},
"devDependencies": {

View File

@@ -1,5 +1,6 @@
{
"extends": "../../tsconfig.json",
"include": ["src", "test"],
"exclude": ["src/index.browser.ts", "test/unit/webEsmBundle.browser.test.ts"]
// These files to be excluded in case we run `check-types` before `build:bundle`
"exclude": ["test/unit/webEsmBundle.browser.test.ts", "test/browser/webEsmBundle.browser.test.ts"]
}

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -63,14 +63,14 @@
},
"types": "lib/index.d.ts",
"dependencies": {
"@lodestar/utils": "^1.29.0",
"@lodestar/utils": "^1.30.0",
"winston": "^3.8.2",
"winston-daily-rotate-file": "^4.7.1",
"winston-transport": "^4.5.0"
},
"devDependencies": {
"@chainsafe/threads": "^1.11.1",
"@lodestar/test-utils": "^1.29.0",
"@lodestar/test-utils": "^1.30.0",
"@types/triple-beam": "^1.3.2",
"triple-beam": "^1.3.0"
},

View File

@@ -1,6 +1,6 @@
{
"name": "@lodestar/params",
"version": "1.29.0",
"version": "1.30.0",
"description": "Chain parameters required for lodestar",
"author": "ChainSafe Systems",
"license": "Apache-2.0",

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -66,13 +66,13 @@
"@ethereumjs/tx": "^4.1.2",
"@ethereumjs/util": "^8.0.6",
"@ethereumjs/vm": "^6.4.2",
"@lodestar/api": "^1.29.0",
"@lodestar/config": "^1.29.0",
"@lodestar/light-client": "^1.29.0",
"@lodestar/logger": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/api": "^1.30.0",
"@lodestar/config": "^1.30.0",
"@lodestar/light-client": "^1.30.0",
"@lodestar/logger": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"ethereum-cryptography": "^2.0.0",
"find-up": "^6.3.0",
"http-proxy": "^1.18.1",
@@ -81,7 +81,7 @@
"yargs": "^17.7.1"
},
"devDependencies": {
"@lodestar/test-utils": "^1.29.0",
"@lodestar/test-utils": "^1.30.0",
"@types/http-proxy": "^1.17.10",
"@types/yargs": "^17.0.24",
"axios": "^1.3.4",

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -54,9 +54,9 @@
"dependencies": {
"@chainsafe/fast-crc32c": "^4.2.0",
"@libp2p/interface": "^2.7.0",
"@lodestar/config": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/config": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"it-all": "^3.0.4",
"it-pipe": "^3.0.1",
"snappy": "^7.2.2",
@@ -65,8 +65,8 @@
"uint8arraylist": "^2.4.7"
},
"devDependencies": {
"@lodestar/logger": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/logger": "^1.30.0",
"@lodestar/types": "^1.30.0",
"libp2p": "2.8.2"
},
"peerDependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@lodestar/spec-test-util",
"version": "1.29.0",
"version": "1.30.0",
"description": "Spec test suite generator from yaml test files",
"author": "ChainSafe Systems",
"license": "Apache-2.0",
@@ -62,15 +62,11 @@
"blockchain"
],
"dependencies": {
"@lodestar/utils": "^1.29.0",
"@lodestar/utils": "^1.30.0",
"rimraf": "^4.4.1",
"snappyjs": "^0.7.0",
"tar": "^6.1.13",
"vitest": "^3.0.6"
},
"devDependencies": {
"@types/tar": "^6.1.4"
},
"peerDependencies": {
"vitest": "^3.0.6"
}

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -65,10 +65,10 @@
"@chainsafe/pubkey-index-map": "^3.0.0",
"@chainsafe/ssz": "^1.2.0",
"@chainsafe/swap-or-not-shuffle": "^1.2.1",
"@lodestar/config": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/config": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"bigint-buffer": "^1.1.5"
},
"keywords": [

View File

@@ -3,7 +3,7 @@ import {Attestation, Slot, electra, phase0, ssz} from "@lodestar/types";
import {toRootHex} from "@lodestar/utils";
import {assert} from "@lodestar/utils";
import {CachedBeaconStateAllForks, CachedBeaconStatePhase0} from "../types.js";
import {computeEpochAtSlot} from "../util/index.js";
import {computeEndSlotAtEpoch, computeEpochAtSlot} from "../util/index.js";
import {isValidIndexedAttestation} from "./index.js";
/**
@@ -78,9 +78,11 @@ export function validateAttestation(fork: ForkSeq, state: CachedBeaconStateAllFo
// post deneb, the attestations are valid till end of next epoch
if (!(data.slot + MIN_ATTESTATION_INCLUSION_DELAY <= slot && isTimelyTarget(fork, slot - data.slot))) {
const windowStart = data.slot + MIN_ATTESTATION_INCLUSION_DELAY;
const windowEnd = fork >= ForkSeq.deneb ? computeEndSlotAtEpoch(computedEpoch + 1) : data.slot + SLOTS_PER_EPOCH;
throw new Error(
"Attestation slot not within inclusion window: " +
`slot=${data.slot} window=${data.slot + MIN_ATTESTATION_INCLUSION_DELAY}..${data.slot + SLOTS_PER_EPOCH}`
`Attestation slot not within inclusion window: slot=${data.slot} window=${windowStart}..${windowEnd}`
);
}

View File

@@ -8,7 +8,7 @@ import {
getFullOrBlindedPayloadFromBody,
isMergeTransitionComplete,
} from "../util/execution.js";
import {getRandaoMix} from "../util/index.js";
import {computeEpochAtSlot, getRandaoMix} from "../util/index.js";
import {BlockExternalData, ExecutionPayloadStatus} from "./externalData.js";
export function processExecutionPayload(
@@ -49,7 +49,7 @@ export function processExecutionPayload(
}
if (isForkPostDeneb(forkName)) {
const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(forkName);
const maxBlobsPerBlock = state.config.getMaxBlobsPerBlock(computeEpochAtSlot(state.slot));
const blobKzgCommitmentsLen = (body as deneb.BeaconBlockBody).blobKzgCommitments?.length ?? 0;
if (blobKzgCommitmentsLen > maxBlobsPerBlock) {
throw Error(`blobKzgCommitmentsLen of ${blobKzgCommitmentsLen} exceeds limit=${maxBlobsPerBlock}`);

View File

@@ -1,7 +1,7 @@
{
"name": "@lodestar/test-utils",
"private": true,
"version": "1.29.0",
"version": "1.30.0",
"description": "Test utilities reused across other packages",
"author": "ChainSafe Systems",
"license": "Apache-2.0",
@@ -59,8 +59,8 @@
"dependencies": {
"@chainsafe/bls-keystore": "^3.1.0",
"@chainsafe/blst": "^2.2.0",
"@lodestar/params": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/params": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"axios": "^1.3.4",
"testcontainers": "^10.2.1",
"tmp": "^0.2.1",

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": {
".": {
@@ -69,7 +69,7 @@
"types": "lib/index.d.ts",
"dependencies": {
"@chainsafe/ssz": "^1.2.0",
"@lodestar/params": "^1.29.0",
"@lodestar/params": "^1.30.0",
"ethereum-cryptography": "^2.0.0"
},
"keywords": [

View File

@@ -11,7 +11,7 @@
"bugs": {
"url": "https://github.com/ChainSafe/lodestar/issues"
},
"version": "1.29.0",
"version": "1.30.0",
"type": "module",
"exports": "./lib/index.js",
"files": [

View File

@@ -1,6 +1,6 @@
{
"name": "@lodestar/validator",
"version": "1.29.0",
"version": "1.30.0",
"description": "A Typescript implementation of the validator client",
"author": "ChainSafe Systems",
"license": "LGPL-3.0",
@@ -47,17 +47,17 @@
"dependencies": {
"@chainsafe/blst": "^2.2.0",
"@chainsafe/ssz": "^1.2.0",
"@lodestar/api": "^1.29.0",
"@lodestar/config": "^1.29.0",
"@lodestar/db": "^1.29.0",
"@lodestar/params": "^1.29.0",
"@lodestar/state-transition": "^1.29.0",
"@lodestar/types": "^1.29.0",
"@lodestar/utils": "^1.29.0",
"@lodestar/api": "^1.30.0",
"@lodestar/config": "^1.30.0",
"@lodestar/db": "^1.30.0",
"@lodestar/params": "^1.30.0",
"@lodestar/state-transition": "^1.30.0",
"@lodestar/types": "^1.30.0",
"@lodestar/utils": "^1.30.0",
"strict-event-emitter-types": "^2.0.0"
},
"devDependencies": {
"@lodestar/test-utils": "^1.29.0",
"@lodestar/test-utils": "^1.30.0",
"bigint-buffer": "^1.1.5",
"rimraf": "^4.4.1"
}

View File

@@ -1,4 +1,4 @@
import {ChainConfig, chainConfigToJson} from "@lodestar/config";
import {ChainConfig, SpecJson, chainConfigToJson} from "@lodestar/config";
import {BeaconPreset, activePreset, presetToJson} from "@lodestar/params";
export class NotEqualParamsError extends Error {}
@@ -20,7 +20,7 @@ type ConfigWithPreset = ChainConfig & BeaconPreset;
* So this check only compares a specific list of parameters that are consensus critical, ignoring the rest. Typed
* config and preset ensure new parameters are labeled critical or ignore, facilitating maintenance of the list.
*/
export function assertEqualParams(localConfig: ChainConfig, externalSpecJson: Record<string, string>): void {
export function assertEqualParams(localConfig: ChainConfig, externalSpecJson: SpecJson): void {
// Before comparing, add preset which is bundled in api impl config route.
// config and preset must be serialized to JSON for safe comparisions.
const localSpecJson = {
@@ -259,6 +259,6 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record<keyof ConfigWit
NODE_CUSTODY_REQUIREMENT: false,
VALIDATOR_CUSTODY_REQUIREMENT: fuluForkRelevant,
BALANCE_PER_ADDITIONAL_CUSTODY_GROUP: fuluForkRelevant,
MAX_BLOBS_PER_BLOCK_FULU: fuluForkRelevant,
BLOB_SCHEDULE: fuluForkRelevant,
};
}

View File

@@ -12095,7 +12095,7 @@ tar@6.1.11:
mkdirp "^1.0.3"
yallist "^4.0.0"
tar@^6.0.2, tar@^6.1.11, tar@^6.1.13, tar@^6.1.2:
tar@^6.0.2, tar@^6.1.11, tar@^6.1.2:
version "6.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==