diff --git a/.wordlist.txt b/.wordlist.txt index 46ca81441348..11a5a3746476 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -45,6 +45,7 @@ Grafana Grandine HTTPS HackMD +Hashicorp Homebrew IPFS IPv @@ -54,6 +55,7 @@ JSObjects JWT KDE Kubernetes +Kurtosis LGPL LGPLv LMD @@ -102,6 +104,7 @@ backfill beaconcha blockRoot blockchain +blst bootnode bootnodes bundlers @@ -139,6 +142,7 @@ ephemery ethers flamegraph flamegraphs +floodsub getNetworkIdentity gnosis heapdump @@ -160,6 +164,7 @@ malloc mdns merkle merkleization +mmeshsub monorepo multiaddr multifork @@ -171,6 +176,7 @@ orchestrator osx overriden params +performant pid plaintext pre @@ -178,6 +184,7 @@ premined produceBlockV protolambda prover +pubsub repo repos req @@ -204,6 +211,7 @@ util utils validator validators +verifier vite vitest webpack diff --git a/Dockerfile b/Dockerfile index c65cac28d51d..e4b90c50bd20 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ # --platform=$BUILDPLATFORM is used build javascript source with host arch # Otherwise TS builds on emulated archs and can be extremely slow (+1h) -FROM --platform=${BUILDPLATFORM:-amd64} node:22.4-alpine as build_src +FROM --platform=${BUILDPLATFORM:-amd64} node:22.4-slim as build_src ARG COMMIT WORKDIR /usr/app -RUN apk update && apk add --no-cache g++ make python3 py3-setuptools && rm -rf /var/cache/apk/* +RUN apt-get update && apt-get install -y g++ make python3 python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* COPY . . @@ -21,21 +21,21 @@ RUN cd packages/cli && GIT_COMMIT=${COMMIT} yarn write-git-data # Copy built src + node_modules to build native packages for archs different than host. # Note: This step is redundant for the host arch -FROM node:22.4-alpine as build_deps +FROM node:22.4-slim as build_deps WORKDIR /usr/app -RUN apk update && apk add --no-cache g++ make python3 py3-setuptools && rm -rf /var/cache/apk/* +RUN apt-get update && apt-get install -y g++ make python3 python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* COPY --from=build_src /usr/app . # Do yarn --force to trigger a rebuild of the native packages -# Emmulates `yarn rebuild` which is not available in v1 https://yarnpkg.com/cli/rebuild +# Emmulates `yarn rebuild` which is not available in v1 https://yarnpkg.com/cli/rebuild RUN yarn install --non-interactive --frozen-lockfile --production --force # Rebuild leveldb bindings (required for arm64 build) RUN cd node_modules/classic-level && yarn rebuild # Copy built src + node_modules to a new layer to prune unnecessary fs # Previous layer weights 7.25GB, while this final 488MB (as of Oct 2020) -FROM node:22.4-alpine +FROM node:22.4-slim WORKDIR /usr/app COPY --from=build_deps /usr/app . diff --git a/README.md b/README.md index 4afb92d4e92d..e03de683a9de 100644 --- a/README.md +++ b/README.md @@ -44,21 +44,25 @@ yarn build - :package: This mono-repository contains a suite of Ethereum Consensus packages. - :balance_scale: The mono-repository is released under [LGPLv3 license](./LICENSE). Note, that the packages contain their own licenses. -| Package | Version | License | Docs | Description | -| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | -| [`@lodestar/beacon-node`](./packages/beacon-node) | [![npm](https://img.shields.io/npm/v/@lodestar/beacon-node)](https://www.npmjs.com/package/@lodestar/beacon-node) | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/beacon-node) | :rotating_light: Beacon-chain client | -| [`@lodestar/validator`](./packages/validator) | [![npm](https://img.shields.io/npm/v/@lodestar/validator)](https://www.npmjs.com/package/@lodestar/validator) | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/validator) | :bank: Validator client | -| [`@lodestar/light-client`](./packages/light-client) | [![npm](https://img.shields.io/npm/v/@lodestar/light-client)](https://www.npmjs.com/package/@lodestar/light-client) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/light-client) | :bird: Ethereum Light client | -| [`@lodestar/api`](./packages/api) | [![npm](https://img.shields.io/npm/v/@lodestar/api)](https://www.npmjs.com/package/@lodestar/api) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/api) | :clipboard: REST Client for the Ethereum Beacon API | -| [`@chainsafe/lodestar`](./packages/cli) | [![npm](https://img.shields.io/npm/v/@chainsafe/lodestar)](https://www.npmjs.com/package/@chainsafe/lodestar) | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/cli/) | :computer: Command-line tool for Lodestar | -| [`@lodestar/state-transition`](./packages/state-transition) | [![npm](https://img.shields.io/npm/v/@lodestar/state-transition)](https://www.npmjs.com/package/@lodestar/state-transition) | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/state-transition) | :mag_right: Eth Consensus beacon-state transition | -| [`@lodestar/types`](./packages/types) | [![npm](https://img.shields.io/npm/v/@lodestar/types)](https://www.npmjs.com/package/@lodestar/types) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/types) | :spiral_notepad: Eth Consensus TypeScript and SSZ types | -| [`@lodestar/params`](./packages/params) | [![npm](https://img.shields.io/npm/v/@lodestar/params)](https://www.npmjs.com/package/@lodestar/params) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/params) | :spider_web: Eth Consensus network parameters | -| [`@lodestar/utils`](./packages/utils) | [![npm](https://img.shields.io/npm/v/@lodestar/utils)](https://www.npmjs.com/package/@lodestar/utils) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/utils) | :toolbox: Miscellaneous utility functions used across Lodestar | -| [`@lodestar/config`](./packages/config) | [![npm](https://img.shields.io/npm/v/@lodestar/config)](https://www.npmjs.com/package/@lodestar/config) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/config) | :spiral_notepad: Eth Consensus types and params bundled together | -| [`@lodestar/spec-test-util`](./packages/spec-test-util) | [![npm](https://img.shields.io/npm/v/@lodestar/spec-test-util)](https://www.npmjs.com/package/@lodestar/spec-test-util) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/spec-test-util) | :test_tube: Test harness for Eth Consensus spec tests | -| [`@lodestar/db`](./packages/db) | [![npm](https://img.shields.io/npm/v/@lodestar/db)](https://www.npmjs.com/package/@lodestar/db) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/db) | :floppy_disk: Read/write persistent Eth Consensus data | -| [`@lodestar/fork-choice`](./packages/fork-choice) | [![npm](https://img.shields.io/npm/v/@lodestar/fork-choice)](https://www.npmjs.com/package/@lodestar/fork-choice) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/fork-choice) | :fork_and_knife: Beacon-chain fork choice | +| Package | Version | License | Docs | Description | +| ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | +| [`@chainsafe/lodestar`](./packages/cli) | [![npm](https://img.shields.io/npm/v/@chainsafe/lodestar)](https://www.npmjs.com/package/@chainsafe/lodestar) | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/cli/) | :computer: Command-line tool for Lodestar | +| [`@lodestar/api`](./packages/api) | [![npm](https://img.shields.io/npm/v/@lodestar/api)](https://www.npmjs.com/package/@lodestar/api) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/api) | :clipboard: REST Client for the Ethereum Beacon API | +| [`@lodestar/beacon-node`](./packages/beacon-node) | [![npm](https://img.shields.io/npm/v/@lodestar/beacon-node)](https://www.npmjs.com/package/@lodestar/beacon-node) | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/beacon-node) | :rotating_light: Beacon-chain client | +| [`@lodestar/config`](./packages/config) | [![npm](https://img.shields.io/npm/v/@lodestar/config)](https://www.npmjs.com/package/@lodestar/config) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/config) | :spiral_notepad: Eth Consensus types and params bundled together | +| [`@lodestar/db`](./packages/db) | [![npm](https://img.shields.io/npm/v/@lodestar/db)](https://www.npmjs.com/package/@lodestar/db) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/db) | :floppy_disk: Read/write persistent Eth Consensus data | +| [`@lodestar/flare`](./packages/flare) | [![npm](https://img.shields.io/npm/v/@lodestar/flare)](https://www.npmjs.com/package/@lodestar/flare) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/flare) | :boom: Command tool for triggering non-standard actions | +| [`@lodestar/fork-choice`](./packages/fork-choice) | [![npm](https://img.shields.io/npm/v/@lodestar/fork-choice)](https://www.npmjs.com/package/@lodestar/fork-choice) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/fork-choice) | :fork_and_knife: Beacon-chain fork choice | +| [`@lodestar/light-client`](./packages/light-client) | [![npm](https://img.shields.io/npm/v/@lodestar/light-client)](https://www.npmjs.com/package/@lodestar/light-client) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/light-client) | :bird: Ethereum Light client | +| [`@lodestar/logger`](./packages/logger) | [![npm](https://img.shields.io/npm/v/@lodestar/logger)](https://www.npmjs.com/package/@lodestar/logger) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/logger) | :memo: NodeJS logger for Lodestar binaries | +| [`@lodestar/params`](./packages/params) | [![npm](https://img.shields.io/npm/v/@lodestar/params)](https://www.npmjs.com/package/@lodestar/params) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/params) | :spider_web: Eth Consensus network parameters | +| [`@lodestar/prover`](./packages/prover) | [![npm](https://img.shields.io/npm/v/@lodestar/prover)](https://www.npmjs.com/package/@lodestar/prover) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/prover) | :white_check_mark: Ethereum Light client verifier for execution JSON-RPC calls | +| [`@lodestar/reqresp`](./packages/reqresp) | [![npm](https://img.shields.io/npm/v/@lodestar/reqresp)](https://www.npmjs.com/package/@lodestar/reqresp) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/reqresp) | :telephone_receiver: Eth Consensus Req/Resp protocol | +| [`@lodestar/spec-test-util`](./packages/spec-test-util) | [![npm](https://img.shields.io/npm/v/@lodestar/spec-test-util)](https://www.npmjs.com/package/@lodestar/spec-test-util) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/spec-test-util) | :test_tube: Test harness for Eth Consensus spec tests | +| [`@lodestar/state-transition`](./packages/state-transition) | [![npm](https://img.shields.io/npm/v/@lodestar/state-transition)](https://www.npmjs.com/package/@lodestar/state-transition) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/state-transition) | :mag_right: Eth Consensus beacon-state transition | +| [`@lodestar/types`](./packages/types) | [![npm](https://img.shields.io/npm/v/@lodestar/types)](https://www.npmjs.com/package/@lodestar/types) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/types) | :spiral_notepad: Eth Consensus TypeScript and SSZ types | +| [`@lodestar/utils`](./packages/utils) | [![npm](https://img.shields.io/npm/v/@lodestar/utils)](https://www.npmjs.com/package/@lodestar/utils) | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/utils) | :toolbox: Miscellaneous utility functions used across Lodestar | +| [`@lodestar/validator`](./packages/validator) | [![npm](https://img.shields.io/npm/v/@lodestar/validator)](https://www.npmjs.com/package/@lodestar/validator) | [![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0) | [![documentation](https://img.shields.io/badge/readme-blue)](./packages/validator) | :bank: Validator client | ## Contributors diff --git a/RELEASE.md b/RELEASE.md index 91b450baa34a..3379b40f3720 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -74,7 +74,7 @@ If there is a bug discovered during the testing period which significantly impac For example: After 3-5 days of testing, is performance equal to or better than latest stable? - **Yes**: Continue to the next release step -- **No**: If it a small issue fixable quickly (hot-fix)? +- **No**: If it a small issue fixable quickly (hotfix)? - **Yes**: Merge fix(es) to `unstable`, push the fix(es) to `rc/v1.1.0` branch, go to step 2, incrementing the rc version - **No**: abort the release. Close the `chore: v1.1.0 release` PR, delete the branch, and start the whole release process over. @@ -106,11 +106,11 @@ Tagging a stable release will trigger CI to publish to NPM, dockerhub, and Githu ### 6. Announce - Double check that Github release is correct -- Publish to Social Media +- Follow [Publish to Social Media](#publish-to-social-media) steps -## Hot-fix release +## Hotfix release -If a stable version requires an immediate hot-fix before the next release, a hot-fix release is started. +If a stable version requires an immediate fix before the next release, a hotfix release is started. A similar process for a stable release is used, with the three differences. @@ -162,7 +162,62 @@ Tagging a release candidate will trigger CI to publish to NPM, dockerhub, and Gi - `git tag -am "v1.1.1-rc.0" v1.1.1-rc.0` - `git push origin v1.1.1-rc.0` -Continue following the "test release candidate" and "merge release candidate" sections. Testing window may be modified depending on the severity of the bug fixed. +### 3. Test hotfix release candidate + +Once a hotfix release candidate is created, the Lodestar team may begin a modified hotfix testing period consisting of a quick sanity check or longer if required. + +If the hotfix does not address the purpose of the hotfix release, or there is another bug discovered during this modified hotfix testing period which significantly impacts performance, security, or stability, and it is determined that it is no longer prudent to promote the `rc.x` candidate to `stable`, then it will await an additional fix by the team. The fix will be committed to `unstable` first, then cherrypicked into the `rc/v1.1.1` hotfix branch. Then we publish and promote the new commit to `rc.x+1`. The modified hotfix testing period will reset. + +For example: After modified hotfix testing period, is the original bug resolved? Is performance equal to or better than latest stable? + +- **Yes**: Continue to the next release step +- **No**: If it a small issue fixable quickly with another hotfix? + - **Yes**: Merge fix(es) to `unstable`, push the fix(es) to `rc/v1.1.1` hotfix branch, go to step 2, incrementing the rc version + - **No**: Abort the release. Close the `chore: v1.1.v release` PR, delete the branch, and start the whole release process over. + +### 4. Merge hotfix release candidate + +- Ensure step 3 testing is successful and there is sufficient consensus to release `v1.1.1`. +- Approving the `chore: v1.1.1 release` PR means a team member marks the release as safe, after personally reviewing and / or testing it. +- Merge `chore: v1.1.1 release` PR to stable **with "merge commit"** strategy to preserve all history. +- Merge `stable` into `unstable` **with merge commit** strategy. Due to branch protections in `unstable` must open a PR. If there are conflicts, those must be resolved manually. Gitflow may cause changes that conflict between stable and unstable, for example due to a hotfix that is backported. If that happens, disable branch protections in unstable, merge locally fixing conflicts, run lint + tests, push, and re-enable branch protections. See "Backporting merge conflicts from stable to unstable". + +Pull the latest commits on both `stable` and `unstable` branches: + +- `git checkout stable && git pull origin stable` +- `git checkout unstable && git pull origin unstable` + +Merge `stable` into `unstable`, resolving conflicts: + +- `git checkout unstable && git merge stable` +- Resolve conflicts +- Sanity check locally before pushing by using: `git diff unstable origin/unstable` +- Disable `unstable` branch protection +- `git push` +- Enable `unstable` branch protection + +### 5. Tag stable hotfix release + +Tagging a stable release will trigger CI to publish to NPM, dockerhub, and Github releases. + +#### All-in-one script (for example version `v1.1.1`): + +- `git checkout stable` +- `yarn release:tag-stable 1.1.1` + - Must be run locally from a write-access account capable of triggering CI. + +#### Manual steps (for example version `v1.1.1`): + +- Check out the new stable + - `git checkout stable` +- Tag it as `v1.1.1` with an annotated tag, push commit and tag. + - `git tag -am "v1.1.1" v1.1.1` + - `git push origin v1.1.1` + +### 6. Announce + +- Double check that Github release is correct +- Follow [Publish to Social Media](#publish-to-social-media) steps ## Dev release @@ -280,7 +335,7 @@ Lodestar used `master` as the single target for feature branches. - QA is done on `v1.1.x` branch - Fixes on rc are done on `v1.1.x`, then re-tag - Once released final `v1.1.0` tag is on a branch that is never merged -- Hot-fixes are either cherry-picked from `master` or done on the `v1.1.x` branch, never merged +- Hotfixes are either cherry-picked from `master` or done on the `v1.1.x` branch, never merged However, this had some issues: diff --git a/dashboards/lodestar_block_processor.json b/dashboards/lodestar_block_processor.json index e29d3522ce60..3258efa72fc0 100644 --- a/dashboards/lodestar_block_processor.json +++ b/dashboards/lodestar_block_processor.json @@ -5101,7 +5101,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_bucket[$rate_interval])", + "expr": "rate(lodestar_gossip_block_gossip_validate_time_bucket[$rate_interval])", "format": "heatmap", "instant": false, "legendFormat": "time", @@ -5273,7 +5273,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_sum[$rate_interval]) / rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_count[$rate_interval])", + "expr": "rate(lodestar_gossip_block_gossip_validate_time_sum[$rate_interval]) / rate(lodestar_gossip_block_gossip_validate_time_count[$rate_interval])", "format": "heatmap", "instant": false, "legendFormat": "time", @@ -7132,7 +7132,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_block_production.json b/dashboards/lodestar_block_production.json index 5e33a72ef9af..58c7ba3f3684 100644 --- a/dashboards/lodestar_block_production.json +++ b/dashboards/lodestar_block_production.json @@ -2211,7 +2211,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_bls_thread_pool.json b/dashboards/lodestar_bls_thread_pool.json index 92b7d00d3783..0fd2572d5a1d 100644 --- a/dashboards/lodestar_bls_thread_pool.json +++ b/dashboards/lodestar_bls_thread_pool.json @@ -1174,9 +1174,9 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_sum[$rate_interval]) * 384", + "expr": "rate(lodestar_bls_thread_pool_aggregate_with_randomness_main_thread_time_seconds_sum[$rate_interval]) * 384", "instant": false, - "legendFormat": "signature_deserialization", + "legendFormat": "aggregate_with_randomness", "range": true, "refId": "A" }, @@ -1270,7 +1270,7 @@ "disableTextWrap": false, "editorMode": "code", "exemplar": false, - "expr": "rate(lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds_bucket[$rate_interval])", + "expr": "rate(lodestar_bls_thread_pool_aggregate_with_randomness_main_thread_time_seconds_bucket[$rate_interval])", "format": "heatmap", "fullMetaSearch": false, "includeNullMetadata": true, @@ -1281,7 +1281,7 @@ "useBackend": false } ], - "title": "Main Thread Signature Aggregation Time", + "title": "Main Thread AggregateWithRandomness Time", "type": "heatmap" }, { @@ -1483,7 +1483,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_debug_gossipsub.json b/dashboards/lodestar_debug_gossipsub.json index 0486bd58fbd3..26cd9b9b791a 100644 --- a/dashboards/lodestar_debug_gossipsub.json +++ b/dashboards/lodestar_debug_gossipsub.json @@ -8876,7 +8876,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_discv5.json b/dashboards/lodestar_discv5.json index 31f115936df2..ca7f300060b4 100644 --- a/dashboards/lodestar_discv5.json +++ b/dashboards/lodestar_discv5.json @@ -2168,7 +2168,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_execution_engine.json b/dashboards/lodestar_execution_engine.json index 2c4cadc131f1..63c1ab636e51 100644 --- a/dashboards/lodestar_execution_engine.json +++ b/dashboards/lodestar_execution_engine.json @@ -3439,7 +3439,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_historical_state_regen.json b/dashboards/lodestar_historical_state_regen.json new file mode 100644 index 000000000000..bc7106bf8de9 --- /dev/null +++ b/dashboards/lodestar_historical_state_regen.json @@ -0,0 +1,2646 @@ +{ + "__inputs": [ + { + "description": "", + "label": "Prometheus", + "name": "DS_PROMETHEUS", + "pluginId": "prometheus", + "pluginName": "Prometheus", + "type": "datasource" + }, + { + "description": "", + "label": "Beacon node job name", + "name": "VAR_BEACON_JOB", + "type": "constant", + "value": "beacon" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 1, + "id": null, + "links": [ + { + "asDropdown": true, + "icon": "external link", + "includeVars": true, + "keepTime": true, + "tags": [ + "lodestar" + ], + "targetBlank": false, + "title": "Lodestar dashboards", + "tooltip": "", + "type": "dashboards", + "url": "" + } + ], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 535, + "panels": [], + "title": "VM", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 536, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "lodestar_historical_state_worker_nodejs_heap_space_size_used_bytes{job=~\"$beacon_job|beacon\"}", + "interval": "", + "legendFormat": "{{space}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "lodestar_historical_state_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"}", + "hide": false, + "interval": "", + "legendFormat": "external_memory", + "range": true, + "refId": "B" + } + ], + "title": "Heap Allocations", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "GC Time", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "C" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "GC Bytes" + }, + { + "id": "unit", + "value": "decbytes" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 537, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.4.0-beta1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_worker_nodejs_gc_pause_seconds_total{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "hide": false, + "legendFormat": "{{gctype}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_worker_nodejs_gc_reclaimed_bytes_total{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "format": "time_series", + "hide": false, + "legendFormat": "{{gctype}}", + "range": true, + "refId": "C" + } + ], + "title": "GC pause time rate + reclaimed bytes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 2, + "type": "log" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "p50" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p90" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "p99" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "min" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "set_immediate" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "max" + }, + "properties": [ + { + "id": "custom.fillBelowTo", + "value": "min" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "id": 538, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "avg_over_time(lodestar_historical_state_worker_nodejs_eventloop_lag_min_seconds{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "hide": false, + "legendFormat": "min", + "range": true, + "refId": "0" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "avg_over_time(lodestar_historical_state_worker_nodejs_eventloop_lag_p50_seconds{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "legendFormat": "p50", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "avg_over_time(lodestar_historical_state_worker_nodejs_eventloop_lag_p90_seconds{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "hide": false, + "legendFormat": "p90", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "avg_over_time(lodestar_historical_state_worker_nodejs_eventloop_lag_p99_seconds{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "hide": false, + "legendFormat": "p99", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "avg_over_time(lodestar_historical_state_worker_nodejs_eventloop_lag_max_seconds{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "hide": false, + "legendFormat": "max", + "range": true, + "refId": "D" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "avg_over_time(lodestar_historical_state_worker_nodejs_eventloop_lag_seconds{job=~\"$beacon_job|beacon\"}[$rate_interval])", + "hide": false, + "legendFormat": "set_immediate", + "range": true, + "refId": "E" + } + ], + "title": "Event loop lag", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 25, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "refId": "A" + } + ], + "title": "Job Queue", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.4.0-beta1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_queue_job_time_seconds_sum[$rate_interval])", + "instant": false, + "interval": "", + "legendFormat": "block_processor", + "refId": "A" + } + ], + "title": "Utilization rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 81, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "12*rate(lodestar_historical_state_queue_job_time_seconds_count[6m])", + "instant": false, + "interval": "", + "legendFormat": "block_processor", + "refId": "A" + } + ], + "title": "Jobs / slot", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 100, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_queue_job_time_seconds_sum[$rate_interval])/rate(lodestar_historical_state_queue_job_time_seconds_count[$rate_interval])", + "instant": false, + "interval": "", + "legendFormat": "block_processor", + "refId": "A" + } + ], + "title": "Job time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 128, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "lodestar_historical_state_queue_length", + "instant": false, + "interval": "", + "legendFormat": "block_processor", + "refId": "A" + } + ], + "title": "Queue length", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 34 + }, + "id": 127, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_queue_job_wait_time_seconds_sum[$rate_interval])/rate(lodestar_historical_state_queue_job_wait_time_seconds_count[$rate_interval])", + "instant": false, + "interval": "", + "legendFormat": "block_processor", + "refId": "A" + } + ], + "title": "Job wait time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 2, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 34 + }, + "id": 126, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_queue_dropped_jobs_total[$rate_interval])/(rate(lodestar_historical_state_queue_job_time_seconds_count[$rate_interval])+rate(lodestar_historical_state_queue_dropped_jobs_total[$rate_interval]))", + "instant": false, + "interval": "", + "legendFormat": "block_processor", + "refId": "A" + } + ], + "title": "Dropped jobs %", + "type": "timeseries" + }, + { + "collapsed": false, + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 42 + }, + "id": 108, + "panels": [], + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "refId": "A" + } + ], + "title": "Beacon state transition", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 528, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Magma", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "32*12*rate(lodestar_historical_state_stfn_epoch_transition_seconds_bucket[$rate_interval])", + "format": "heatmap", + "interval": "", + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Epoch transition time", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 43 + }, + "id": 529, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Magma", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "12*rate(lodestar_historical_state_stfn_process_block_seconds_bucket[$rate_interval])", + "format": "heatmap", + "interval": "", + "legendFormat": "{{le}}", + "range": true, + "refId": "A" + } + ], + "title": "Process block time", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 51 + }, + "id": 120, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(lodestar_historical_state_stfn_epoch_transition_seconds_sum[$rate_interval])\n/\nrate(lodestar_historical_state_stfn_epoch_transition_seconds_count[$rate_interval])", + "interval": "", + "legendFormat": "epoch transition", + "range": true, + "refId": "A" + } + ], + "title": "Epoch transition avg time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 51 + }, + "id": 121, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_process_block_seconds_sum[$rate_interval])\n/\nrate(lodestar_historical_state_stfn_process_block_seconds_count[$rate_interval])", + "interval": "", + "legendFormat": "process block time", + "range": true, + "refId": "A" + } + ], + "title": "Process block avg time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 59 + }, + "id": 534, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_epoch_transition_step_seconds_sum[$rate_interval])\n/\nrate(lodestar_historical_state_stfn_epoch_transition_step_seconds_count[$rate_interval])", + "instant": false, + "legendFormat": "{{step}}", + "range": true, + "refId": "A" + } + ], + "title": "Epoch Transition By Steps", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 59 + }, + "id": 525, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_process_block_commit_seconds_sum[$rate_interval])\n/\nrate(lodestar_historical_state_stfn_process_block_commit_seconds_count[$rate_interval])", + "interval": "", + "legendFormat": "process block time", + "range": true, + "refId": "A" + } + ], + "title": "Process block commit step avg time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 4, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 67 + }, + "id": 524, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(lodestar_historical_state_stfn_epoch_transition_commit_seconds_sum[$rate_interval])\n/\nrate(lodestar_historical_state_stfn_epoch_transition_commit_seconds_count[$rate_interval])", + "interval": "", + "legendFormat": "epoch transition", + "range": true, + "refId": "A" + } + ], + "title": "Epoch transition commit step avg time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "process block time" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 67 + }, + "id": 123, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(lodestar_historical_state_stfn_process_block_seconds_sum[6m])", + "interval": "", + "legendFormat": "process block time", + "range": true, + "refId": "A" + } + ], + "title": "Process block utilization rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "process block time" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 75 + }, + "id": 122, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "rate(lodestar_historical_state_stfn_epoch_transition_seconds_sum[13m])", + "interval": "", + "legendFormat": "process block time", + "range": true, + "refId": "A" + } + ], + "title": "Epoch transition utilization rate", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "process block time" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 75 + }, + "id": 125, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "12*rate(lodestar_historical_state_stfn_process_block_seconds_count[6m])", + "interval": "", + "legendFormat": "process block time", + "range": true, + "refId": "A" + } + ], + "title": "process block / slot", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "number of epoch transition" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 83 + }, + "id": 124, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "384 * rate(lodestar_historical_state_stfn_epoch_transition_seconds_count[13m])", + "interval": "", + "legendFormat": "number of epoch transition", + "range": true, + "refId": "A" + } + ], + "title": "Epoch transitions / epoch", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 83 + }, + "id": 527, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_validators_nodes_populated_miss_total{source=\"stateTransition\"} [$rate_interval])\n/on(instance)\nrate(lodestar_historical_state_stfn_state_cloned_count_count[$rate_interval])", + "interval": "", + "legendFormat": "stateTransition-validators", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_balances_nodes_populated_miss_total{source=\"stateTransition\"} [$rate_interval])\n/on(instance)\nrate(lodestar_historical_state_stfn_state_cloned_count_count[$rate_interval])", + "hide": false, + "legendFormat": "stateTransition-balances", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_validators_nodes_populated_miss_total{source=\"processSlots\"} [$rate_interval])\n/on(instance)\nrate(lodestar_historical_state_stfn_state_cloned_count_count[$rate_interval])", + "hide": false, + "legendFormat": "processSlots-validators", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_balances_nodes_populated_miss_total{source=\"processSlots\"} [$rate_interval])\n/on(instance)\nrate(lodestar_historical_state_stfn_state_cloned_count_count[$rate_interval])", + "hide": false, + "legendFormat": "processSlots-balances", + "range": true, + "refId": "D" + } + ], + "title": "State SSZ cache miss rate on preState", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 22, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 91 + }, + "id": 526, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_hash_tree_root_seconds_sum[$rate_interval])\n/ on(source)\nrate(lodestar_historical_state_stfn_hash_tree_root_seconds_count[$rate_interval])", + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "State hash_tree_root avg time", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 91 + }, + "id": 523, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Magma", + "steps": 50 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false + } + }, + "pluginVersion": "10.1.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_state_cloned_count_bucket[$rate_interval])", + "format": "heatmap", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Clone count per state", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 99 + }, + "id": 521, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_balances_nodes_populated_miss_total{source=\"processSlots\"} [$rate_interval])\n/\nrate(lodestar_historical_state_stfn_state_clone_total[$rate_interval])", + "legendFormat": "balances-{{source}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_balances_nodes_populated_miss_total{source=\"stateTransition\"} [$rate_interval])\n/\nrate(lodestar_historical_state_stfn_state_clone_total[$rate_interval])", + "hide": false, + "legendFormat": "balances-{{source}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_validators_nodes_populated_miss_total{source=\"processSlots\"} [$rate_interval])\n/\nrate(lodestar_historical_state_stfn_state_clone_total[$rate_interval])", + "hide": false, + "legendFormat": "validators-{{source}}", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_stfn_validators_nodes_populated_miss_total{source=\"stateTransition\"} [$rate_interval])\n/\nrate(lodestar_historical_state_stfn_state_clone_total[$rate_interval])", + "hide": false, + "legendFormat": "validators-{{source}}", + "range": true, + "refId": "D" + } + ], + "title": "State transition fn nodes populated cache miss", + "type": "timeseries" + } + ], + "refresh": "10s", + "schemaVersion": 38, + "style": "dark", + "tags": [ + "lodestar" + ], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "default", + "value": "default" + }, + "hide": 0, + "includeAll": false, + "label": "datasource", + "multi": false, + "name": "DS_PROMETHEUS", + "options": [], + "query": "prometheus", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "auto": true, + "auto_count": 30, + "auto_min": "10s", + "current": { + "selected": false, + "text": "1h", + "value": "1h" + }, + "hide": 0, + "label": "rate() interval", + "name": "rate_interval", + "options": [ + { + "selected": false, + "text": "auto", + "value": "$__auto_interval_rate_interval" + }, + { + "selected": false, + "text": "1m", + "value": "1m" + }, + { + "selected": false, + "text": "10m", + "value": "10m" + }, + { + "selected": false, + "text": "30m", + "value": "30m" + }, + { + "selected": true, + "text": "1h", + "value": "1h" + }, + { + "selected": false, + "text": "6h", + "value": "6h" + }, + { + "selected": false, + "text": "12h", + "value": "12h" + }, + { + "selected": false, + "text": "1d", + "value": "1d" + }, + { + "selected": false, + "text": "7d", + "value": "7d" + }, + { + "selected": false, + "text": "14d", + "value": "14d" + }, + { + "selected": false, + "text": "30d", + "value": "30d" + } + ], + "query": "1m,10m,30m,1h,6h,12h,1d,7d,14d,30d", + "queryValue": "", + "refresh": 2, + "skipUrlSync": false, + "type": "interval" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus_local" + }, + "filters": [ + { + "condition": "", + "key": "instance", + "operator": "=", + "value": "unstable-lg1k-hzax41-dkr" + } + ], + "hide": 0, + "name": "Filters", + "skipUrlSync": false, + "type": "adhoc" + }, + { + "description": "Job name used in Prometheus config to scrape Beacon node", + "hide": 2, + "label": "Beacon node job name", + "name": "beacon_job", + "query": "${VAR_BEACON_JOB}", + "skipUrlSync": false, + "type": "constant" + } + ] + }, + "time": { + "from": "now-24h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "utc", + "title": "Lodestar - historical state regen", + "uid": "lodestar_historical_state_regen", + "version": 32, + "weekStart": "monday" +} diff --git a/dashboards/lodestar_libp2p.json b/dashboards/lodestar_libp2p.json index 0fe72e0a4032..8d391dee2bbe 100644 --- a/dashboards/lodestar_libp2p.json +++ b/dashboards/lodestar_libp2p.json @@ -1348,7 +1348,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_multinode.json b/dashboards/lodestar_multinode.json index 9a8fecaf0128..6c7f18dc3148 100644 --- a/dashboards/lodestar_multinode.json +++ b/dashboards/lodestar_multinode.json @@ -844,7 +844,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_networking.json b/dashboards/lodestar_networking.json index d1c40e659194..a239d47ac62a 100644 --- a/dashboards/lodestar_networking.json +++ b/dashboards/lodestar_networking.json @@ -6421,7 +6421,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_rest_api.json b/dashboards/lodestar_rest_api.json index 6445873f4dc5..7ef29bb89ec5 100644 --- a/dashboards/lodestar_rest_api.json +++ b/dashboards/lodestar_rest_api.json @@ -649,7 +649,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_state_cache_regen.json b/dashboards/lodestar_state_cache_regen.json index be52d414ea3b..e7f6336eaeb9 100644 --- a/dashboards/lodestar_state_cache_regen.json +++ b/dashboards/lodestar_state_cache_regen.json @@ -3088,7 +3088,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_summary.json b/dashboards/lodestar_summary.json index 5e8773c05d4e..87eaed30bd3e 100644 --- a/dashboards/lodestar_summary.json +++ b/dashboards/lodestar_summary.json @@ -2929,7 +2929,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_sync.json b/dashboards/lodestar_sync.json index 6cc82bedde47..f58ccf308f9b 100644 --- a/dashboards/lodestar_sync.json +++ b/dashboards/lodestar_sync.json @@ -1776,7 +1776,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_validator_client.json b/dashboards/lodestar_validator_client.json index 8ec6a04437b1..5e4459d1d1b9 100644 --- a/dashboards/lodestar_validator_client.json +++ b/dashboards/lodestar_validator_client.json @@ -71,6 +71,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "msg / slot", @@ -211,10 +212,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "name" + "textMode": "name", + "wideLayout": true }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -231,7 +234,6 @@ } ], "title": "Lodestar version", - "transformations": [], "type": "stat" }, { @@ -267,10 +269,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "name" + "textMode": "name", + "wideLayout": true }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -321,10 +325,12 @@ "fields": "", "values": false }, + "showPercentChange": false, "text": {}, - "textMode": "name" + "textMode": "name", + "wideLayout": true }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -359,7 +365,7 @@ }, "gridPos": { "h": 2, - "w": 4, + "w": 2, "x": 20, "y": 0 }, @@ -376,9 +382,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -395,6 +403,83 @@ "title": "VC indices", "type": "stat" }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [ + { + "options": { + "0": { + "color": "green", + "index": 0, + "text": "Ready" + }, + "1": { + "color": "yellow", + "index": 1, + "text": "Syncing" + }, + "2": { + "color": "red", + "index": 2, + "text": "Error" + } + }, + "type": "value" + } + ] + }, + "overrides": [] + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 22, + "y": 0 + }, + "id": 47, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "10.4.1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "vc_beacon_health", + "instant": false, + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Beacon health", + "type": "stat" + }, { "datasource": { "type": "prometheus", @@ -429,9 +514,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -482,9 +569,11 @@ "fields": "", "values": false }, - "textMode": "auto" + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -622,7 +711,7 @@ }, "showHeader": false }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "targets": [ { "datasource": { @@ -701,6 +790,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -866,6 +956,7 @@ "mode": "palette-classic" }, "custom": { + "axisBorderShow": false, "axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", @@ -1019,7 +1110,8 @@ }, "showValue": "never", "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -1028,7 +1120,7 @@ "unit": "s" } }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "reverseYBuckets": false, "targets": [ { @@ -1133,7 +1225,8 @@ }, "showValue": "never", "tooltip": { - "show": true, + "mode": "single", + "showColorScale": false, "yHistogram": false }, "yAxis": { @@ -1142,7 +1235,7 @@ "unit": "s" } }, - "pluginVersion": "10.1.1", + "pluginVersion": "10.4.1", "reverseYBuckets": false, "targets": [ { @@ -2344,8 +2437,7 @@ ], "refresh": "10s", "revision": 1, - "schemaVersion": 38, - "style": "dark", + "schemaVersion": 39, "tags": [ "lodestar" ], @@ -2455,7 +2547,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_validator_monitor.json b/dashboards/lodestar_validator_monitor.json index 7579a595b550..3305e6796115 100644 --- a/dashboards/lodestar_validator_monitor.json +++ b/dashboards/lodestar_validator_monitor.json @@ -500,7 +500,7 @@ "type": "prometheus", "uid": "${DS_PROMETHEUS}" }, - "description": "Percent of attestations having correct head.", + "description": "Percent of attestations having incorrect head.", "fieldConfig": { "defaults": { "color": { @@ -1896,7 +1896,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/dashboards/lodestar_vm_host.json b/dashboards/lodestar_vm_host.json index 3f6c18bc19c1..23e1c0418aba 100644 --- a/dashboards/lodestar_vm_host.json +++ b/dashboards/lodestar_vm_host.json @@ -177,7 +177,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))\nor\n(sum(nodejs_heap_size_total_bytes) + sum(discv5_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))\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))", "hide": false, "interval": "", "legendFormat": "node allocated heap", @@ -191,7 +191,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)) \nor\n(sum(nodejs_heap_size_used_bytes) + sum(discv5_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)) \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)) ", "hide": false, "interval": "", "legendFormat": "node used heap", @@ -205,7 +205,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))\nor\n(sum(nodejs_external_memory_bytes) + sum(discv5_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))\nor\n(sum(nodejs_external_memory_bytes) + sum(discv5_worker_nodejs_external_memory_bytes) + sum(lodestar_historical_state_worker_nodejs_external_memory_bytes))", "hide": false, "interval": "", "legendFormat": "node external memory", @@ -291,7 +291,7 @@ "uid": "${DS_PROMETHEUS}" }, "editorMode": "code", - "expr": "sum by (space) ({__name__=~\"nodejs_heap_space_size_used_bytes|network_worker_nodejs_heap_space_size_used_bytes|discv5_worker_nodejs_heap_space_size_used_bytes\"})", + "expr": "sum by (space) ({__name__=~\"nodejs_heap_space_size_used_bytes|network_worker_nodejs_heap_space_size_used_bytes|discv5_worker_nodejs_heap_space_size_used_bytes|lodestar_historical_state_worker_nodejs_heap_space_size_used_bytes\")", "hide": false, "interval": "", "legendFormat": "{{space}}", @@ -305,7 +305,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\"})\nor\n(nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"} + discv5_worker_nodejs_external_memory_bytes{job=~\"$beacon_job|beacon\"})", + "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\"})", "hide": false, "interval": "", "legendFormat": "external_memory", @@ -786,6 +786,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -883,6 +884,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1000,6 +1002,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1027,6 +1030,222 @@ "x": 0, "y": 34 }, + "id": 562, + "options": { + "graph": {}, + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "7.4.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "lodestar_historical_state_worker_nodejs_heap_space_size_used_bytes", + "interval": "", + "legendFormat": "{{space}}", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "exemplar": false, + "expr": "lodestar_historical_state_worker_nodejs_external_memory_bytes", + "hide": false, + "interval": "", + "legendFormat": "external_memory", + "range": true, + "refId": "B" + } + ], + "title": "Historical State Worker Thread - Heap Allocations", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "GC Time", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 20, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byFrameRefID", + "options": "C" + }, + "properties": [ + { + "id": "custom.axisPlacement", + "value": "right" + }, + { + "id": "custom.axisLabel", + "value": "GC Bytes" + }, + { + "id": "unit", + "value": "decbytes" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 34 + }, + "id": 563, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "8.4.0-beta1", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_worker_nodejs_gc_pause_seconds_total[$rate_interval])", + "hide": false, + "legendFormat": "{{gctype}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "rate(lodestar_historical_state_worker_nodejs_gc_reclaimed_bytes_total[$rate_interval])", + "format": "time_series", + "hide": false, + "legendFormat": "{{gctype}}", + "range": true, + "refId": "C" + } + ], + "title": "HIstorical State Worker Thread - GC pause time rate + reclaimed bytes", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "graph": false, + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 42 + }, "id": 542, "options": { "graph": {}, @@ -1097,6 +1316,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1143,7 +1363,7 @@ "h": 8, "w": 12, "x": 12, - "y": 34 + "y": 42 }, "id": 548, "options": { @@ -1199,7 +1419,7 @@ "h": 1, "w": 24, "x": 0, - "y": 42 + "y": 50 }, "id": 12, "panels": [], @@ -1239,6 +1459,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1368,7 +1589,7 @@ "h": 8, "w": 12, "x": 0, - "y": 43 + "y": 51 }, "id": 555, "options": { @@ -1483,6 +1704,7 @@ "tooltip": false, "viz": false }, + "insertNulls": false, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, @@ -1612,7 +1834,7 @@ "h": 8, "w": 12, "x": 12, - "y": 43 + "y": 51 }, "id": 559, "options": { @@ -1856,7 +2078,7 @@ "h": 8, "w": 12, "x": 0, - "y": 51 + "y": 59 }, "id": 560, "options": { @@ -2100,7 +2322,7 @@ "h": 8, "w": 12, "x": 12, - "y": 51 + "y": 59 }, "id": 561, "options": { @@ -2241,7 +2463,7 @@ "h": 8, "w": 12, "x": 0, - "y": 59 + "y": 67 }, "id": 6, "options": { @@ -2364,7 +2586,7 @@ "h": 8, "w": 12, "x": 12, - "y": 59 + "y": 67 }, "id": 42, "options": { @@ -2443,7 +2665,7 @@ "h": 8, "w": 12, "x": 12, - "y": 67 + "y": 75 }, "id": 268, "options": { @@ -2484,7 +2706,7 @@ "h": 1, "w": 24, "x": 0, - "y": 75 + "y": 83 }, "id": 104, "panels": [], @@ -2550,7 +2772,7 @@ "h": 6, "w": 12, "x": 0, - "y": 76 + "y": 84 }, "id": 102, "options": { @@ -2659,7 +2881,7 @@ "h": 6, "w": 12, "x": 12, - "y": 76 + "y": 84 }, "id": 172, "options": { @@ -2744,7 +2966,7 @@ "h": 6, "w": 12, "x": 0, - "y": 82 + "y": 90 }, "id": 171, "options": { @@ -2787,7 +3009,7 @@ "h": 6, "w": 12, "x": 12, - "y": 82 + "y": 90 }, "id": 99, "options": { @@ -2828,7 +3050,7 @@ "h": 1, "w": 24, "x": 0, - "y": 88 + "y": 96 }, "id": 367, "panels": [], @@ -2894,7 +3116,7 @@ "h": 8, "w": 12, "x": 0, - "y": 89 + "y": 97 }, "id": 353, "options": { @@ -2977,7 +3199,7 @@ "h": 8, "w": 12, "x": 12, - "y": 89 + "y": 97 }, "id": 355, "options": { @@ -3059,7 +3281,7 @@ "h": 8, "w": 12, "x": 0, - "y": 97 + "y": 105 }, "id": 357, "options": { @@ -3522,7 +3744,7 @@ "h": 8, "w": 12, "x": 12, - "y": 97 + "y": 105 }, "id": 359, "links": [], @@ -3665,7 +3887,7 @@ "h": 8, "w": 12, "x": 0, - "y": 105 + "y": 113 }, "id": 361, "options": { @@ -3774,7 +3996,7 @@ "h": 8, "w": 12, "x": 12, - "y": 105 + "y": 113 }, "id": 363, "options": { @@ -3866,7 +4088,7 @@ "h": 8, "w": 12, "x": 0, - "y": 113 + "y": 121 }, "id": 365, "options": { @@ -3919,7 +4141,7 @@ "h": 1, "w": 24, "x": 0, - "y": 121 + "y": 129 }, "id": 46, "panels": [], @@ -3985,7 +4207,7 @@ "h": 8, "w": 12, "x": 0, - "y": 122 + "y": 130 }, "id": 48, "options": { @@ -4112,7 +4334,7 @@ "h": 8, "w": 12, "x": 12, - "y": 122 + "y": 130 }, "id": 50, "options": { @@ -4155,7 +4377,7 @@ "h": 1, "w": 24, "x": 0, - "y": 130 + "y": 138 }, "id": 527, "panels": [], @@ -4221,7 +4443,7 @@ "h": 8, "w": 12, "x": 0, - "y": 131 + "y": 139 }, "id": 529, "options": { @@ -4366,7 +4588,7 @@ "h": 8, "w": 12, "x": 12, - "y": 131 + "y": 139 }, "id": 531, "options": { @@ -4410,7 +4632,7 @@ "h": 1, "w": 24, "x": 0, - "y": 139 + "y": 147 }, "id": 86, "panels": [], @@ -4475,7 +4697,7 @@ "h": 11, "w": 12, "x": 0, - "y": 140 + "y": 148 }, "id": 84, "options": { @@ -4558,7 +4780,7 @@ "h": 11, "w": 12, "x": 12, - "y": 140 + "y": 148 }, "id": 87, "options": { @@ -4639,7 +4861,7 @@ "h": 8, "w": 12, "x": 0, - "y": 151 + "y": 159 }, "id": 516, "options": { @@ -4730,7 +4952,7 @@ "h": 8, "w": 12, "x": 12, - "y": 151 + "y": 159 }, "id": 515, "options": { @@ -4809,7 +5031,7 @@ "h": 8, "w": 12, "x": 0, - "y": 159 + "y": 167 }, "id": 513, "options": { @@ -4888,7 +5110,7 @@ "h": 8, "w": 12, "x": 12, - "y": 159 + "y": 167 }, "id": 517, "options": { @@ -4928,7 +5150,7 @@ "h": 1, "w": 24, "x": 0, - "y": 167 + "y": 175 }, "id": 164, "panels": [], @@ -4995,7 +5217,7 @@ "h": 9, "w": 12, "x": 0, - "y": 168 + "y": 176 }, "id": 160, "options": { @@ -5079,7 +5301,7 @@ "h": 9, "w": 12, "x": 12, - "y": 168 + "y": 176 }, "id": 162, "options": { @@ -5163,7 +5385,7 @@ "h": 9, "w": 12, "x": 0, - "y": 177 + "y": 185 }, "id": 481, "options": { @@ -5247,7 +5469,7 @@ "h": 9, "w": 12, "x": 12, - "y": 177 + "y": 185 }, "id": 480, "options": { @@ -5354,7 +5576,7 @@ "h": 8, "w": 12, "x": 0, - "y": 186 + "y": 194 }, "id": 535, "options": { @@ -5459,7 +5681,7 @@ "h": 8, "w": 12, "x": 12, - "y": 186 + "y": 194 }, "id": 537, "options": { @@ -5540,7 +5762,7 @@ "h": 8, "w": 12, "x": 0, - "y": 194 + "y": 202 }, "id": 533, "options": { @@ -5702,7 +5924,7 @@ "condition": "", "key": "instance", "operator": "=", - "value": "unstable-lg1k-hzax41" + "value": "unstable-lg1k-hzax41-dkr" } ], "hide": 0, diff --git a/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md b/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md index f7c4bfece237..04cac75c4b0a 100644 --- a/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md +++ b/docs/pages/contribution/advanced-topics/setting-up-a-testnet.md @@ -6,6 +6,10 @@ title: Setting Up a Testnet To quickly test and run Lodestar we recommend starting a local testnet. We recommend a simple configuration of two beacon nodes with multiple validators. The [dev scripts](https://github.com/ChainSafe/lodestar/tree/unstable/scripts/dev) can used for simplicity but below instructions provide more insights on how it works and include details about different configurations. +:::note +The testnet set up in this guide is meant to be short-lived / ephemeral and should primarily be used for development and testing. Please refer to [Ethereum In a Box](https://github.com/rocknet/ethiab) or [Kurtosis ethereum package](https://github.com/ethpandaops/ethereum-package) to set up a long-lived private network or devnet. +::: + **Terminal 1** Run a beacon node as a **bootnode**, with 8 validators with the following command. diff --git a/docs/pages/contribution/depgraph.md b/docs/pages/contribution/depgraph.md index c0e3d232660f..17675cf47ff0 100644 --- a/docs/pages/contribution/depgraph.md +++ b/docs/pages/contribution/depgraph.md @@ -2,118 +2,252 @@ title: Dependency Graph --- -## Lodestar monorepo dependency graph +## Lodestar Dependency Graph -This is a diagram of the various `lodestar-*` packages in the Lodestar monorepo and how they fit together: +This is a diagram of the various Lodestar packages in the monorepo, how they fit together and interact with major external dependencies: -:::info -note: this dependency graph only applies to dependencies as they are used in the `src/` folders of each package, not in `test/` +:::note +This dependency graph only applies to dependencies as they are used in the `src/` folders of each package, not in `test/` ::: ```mermaid graph TD - lodestar["lodestar"]:::nodemodule - cli["lodestar-cli"]:::nodemodule - config["lodestar-config"]:::nodemodule - db["lodestar-db"]:::nodemodule - fork-choice["lodestar-fork-choice"]:::nodemodule - params["lodestar-params"]:::nodemodule - types["lodestar-types"]:::nodemodule - utils["lodestar-utils"]:::nodemodule - validator["lodestar-validator"]:::nodemodule - state-trans["lodestar-state-transition"]:::nodemodule - + api["api"]:::nodemodule + light-client["light-client"]:::nodemodule + prover["prover"]:::nodemodule + logger["logger"]:::nodemodule + reqresp["reqresp"]:::nodemodule + beacon-node["beacon-node"]:::nodemodule + cli["cli"]:::nodemodule + config["config"]:::nodemodule + db["db"]:::nodemodule + fork-choice["fork-choice"]:::nodemodule + params["params"]:::nodemodule + types["types"]:::nodemodule + utils["utils"]:::nodemodule + validator["validator"]:::nodemodule + state-transition["state-transition"]:::nodemodule + ssz["ssz"]:::nodemodule + blst["blst"]:::nodemodule + discv5["discv5"]:::nodemodule + libp2p["libp2p"]:::nodemodule + libp2p-gossipsub["libp2p-gossipsub"]:::nodemodule + libp2p-noise["libp2p-noise"]:::nodemodule + libp2p-yamux["libp2p-yamux"]:::nodemodule + + ssz-->api + ssz-->config + ssz-->types + ssz-->beacon-node + ssz-->validator + ssz-->light-client + ssz-->state-transition + + blst-->beacon-node + blst-->state-transition + + discv5-->beacon-node + + libp2p-->beacon-node + + libp2p-gossipsub-->libp2p + libp2p-noise-->libp2p + libp2p-yamux-->libp2p + + api-->beacon-node + api-->validator + api-->light-client + + light-client-->prover + + params-->api params-->config params-->types + params-->beacon-node + params-->validator + params-->light-client + params-->prover - types-->lodestar + types-->api + types-->beacon-node types-->cli types-->config types-->validator types-->fork-choice + types-->light-client + types-->prover - config-->lodestar + config-->api + config-->beacon-node config-->cli config-->validator config-->fork-choice - config-->state-trans + config-->state-transition config-->db + config-->light-client + config-->prover - utils-->lodestar + utils-->api + utils-->beacon-node utils-->db utils-->cli utils-->validator utils-->fork-choice - utils-->state-trans + utils-->state-transition + utils-->light-client + + logger-->beacon-node + logger-->validator + logger-->light-client + logger-->prover + logger-->cli + + reqresp-->beacon-node - state-trans-->lodestar - state-trans-->validator - state-trans-->fork-choice + state-transition-->beacon-node + state-transition-->validator + state-transition-->fork-choice - db-->lodestar + db-->beacon-node db-->validator - fork-choice-->lodestar + fork-choice-->beacon-node - lodestar-->cli + beacon-node-->cli validator-->cli + light-client-->cli + click api "https://github.com/ChainSafe/lodestar/tree/unstable/packages/api" + click light-client "https://github.com/ChainSafe/lodestar/tree/unstable/packages/light-client" + click prover "https://github.com/ChainSafe/lodestar/tree/unstable/packages/prover" + click logger "https://github.com/ChainSafe/lodestar/tree/unstable/packages/logger" + click reqresp "https://github.com/ChainSafe/lodestar/tree/unstable/packages/reqresp" click cli "https://github.com/ChainSafe/lodestar/tree/unstable/packages/cli" - click lodestar "https://github.com/ChainSafe/lodestar/tree/unstable/packages/beacon-node" + click beacon-node "https://github.com/ChainSafe/lodestar/tree/unstable/packages/beacon-node" click validator "https://github.com/ChainSafe/lodestar/tree/unstable/packages/validator" click db "https://github.com/ChainSafe/lodestar/tree/unstable/packages/db" click params "https://github.com/ChainSafe/lodestar/tree/unstable/packages/params" - click state-trans "https://github.com/ChainSafe/lodestar/tree/unstable/packages/state-transition" + click state-transition "https://github.com/ChainSafe/lodestar/tree/unstable/packages/state-transition" click fork-choice "https://github.com/ChainSafe/lodestar/tree/unstable/packages/fork-choice" click types "https://github.com/ChainSafe/lodestar/tree/unstable/packages/types" click utils "https://github.com/ChainSafe/lodestar/tree/unstable/packages/utils" click config "https://github.com/ChainSafe/lodestar/tree/unstable/packages/config" - - classDef nodemodule fill:grey,stroke-width:2px,stroke:black,color:white; - linkStyle default stroke:grey, fill:none,stroke-width:1.5px; + click ssz "https://github.com/ChainSafe/ssz" + click blst "https://github.com/ChainSafe/blst-ts" + click discv5 "https://github.com/ChainSafe/discv5" + click libp2p "https://github.com/libp2p/js-libp2p" + click libp2p-gossipsub "https://github.com/ChainSafe/js-libp2p-gossipsub" + click libp2p-noise "https://github.com/ChainSafe/js-libp2p-noise" + click libp2p-yamux "https://github.com/ChainSafe/js-libp2p-yamux" + + classDef nodemodule fill:grey,stroke-width:4px,font-size:48px,stroke:black,color:white; + linkStyle default stroke:grey,fill:none,stroke-width:4px; ``` -For a list of all the packages in the monorepo and a description for each, click [here](https://github.com/ChainSafe/lodestar#packages). +## Lodestar Monorepo -Let's talk about how each package fits together in finer detail, from top to bottom, following the chart. +For a list of all the packages in the monorepo and a description for each, click [here](https://github.com/ChainSafe/lodestar#architecture-overview). +Below is a brief summary of each package in alphabetical order. -## `@lodestar/params` +### `@chainsafe/lodestar` -[@lodestar/params](https://github.com/ChainSafe/lodestar/tree/unstable/packages/params) contains the parameters for configuring an Ethereum Consensus network. For example, the [mainnet params](https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/beacon-chain.md#configuration) +[@chainsafe/lodestar](https://github.com/ChainSafe/lodestar/tree/unstable/packages/cli) combines everything together for CLI usage and configuration of the beacon node and validator. -## `@lodestar/types` +### `@lodestar/api` -[@lodestar/types](https://github.com/ChainSafe/lodestar/tree/unstable/packages/types) contains Ethereum Consensus ssz types and data structures. +[@lodestar/api](https://github.com/ChainSafe/lodestar/tree/unstable/packages/api) contains a TypeScript REST client for the [Ethereum Consensus API](https://github.com/ethereum/beacon-apis). -## `@lodestar/config` +### `@lodestar/beacon-node` -[@lodestar/config](https://github.com/ChainSafe/lodestar/tree/unstable/packages/config) combines `@lodestar/params` and `@lodestar/types` together to be used as a single config object across the other Lodestar packages. +[@lodestar/beacon-node](https://github.com/ChainSafe/lodestar/tree/unstable/packages/beacon-node) contains the actual beacon node process itself, which is the aggregate of all the above packages and the "brain" of the Lodestar beacon chain implementation. All of the node modules live in this package as well. -## `@lodestar/utils` +### `@lodestar/config` -[@lodestar/utils](https://github.com/ChainSafe/lodestar/tree/unstable/packages/utils) contains various utilities that are common among the various Lodestar monorepo packages. +[@lodestar/config](https://github.com/ChainSafe/lodestar/tree/unstable/packages/config) combines `@lodestar/params` and `@lodestar/types` together to be used as a single config object across the other Lodestar packages. -## `@lodestar/state-transition` +### `@lodestar/db` -[@lodestar/state-transition](https://github.com/ChainSafe/lodestar/tree/unstable/packages/state-transition) contains the Lodestar implementation of the [beacon state transition function](https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/beacon-chain.md#beacon-chain-state-transition-function), which is used by `@lodestar/beacon-node` to perform the actual beacon state transition. This package also contains various functions used to calculate info about the beacon chain (such as `computeEpochAtSlot`) which are used by `@lodestar/fork-choice` and `@lodestar/validator` +[@lodestar/db](https://github.com/ChainSafe/lodestar/tree/unstable/packages/db) is where all persistent data about the beacon node is stored. Any package that needs to read or write persistent beacon node data depends on `@lodestar/db`. -## `@lodestar/db` +### `@lodestar/flare` -[@lodestar/db](https://github.com/ChainSafe/lodestar/tree/unstable/packages/db) is where all persistent data about the beacon node is stored. Any package that needs to read or write persistent beacon node data depends on `lodestar-db`. +[@lodestar/flare](https://github.com/ChainSafe/lodestar/tree/unstable/packages/flare) is a command tool used for easily triggering non-standard actions and debugging for researchers, developers and testers. Use with care. -## `@lodestar/fork-choice` +### `@lodestar/fork-choice` [@lodestar/fork-choice](https://github.com/ChainSafe/lodestar/tree/unstable/packages/fork-choice) holds the methods for reading/writing the fork choice DAG. The `@lodestar/beacon-node` package is the sole consumer of this package because the beacon node itself is what controls when the fork choice DAG is updated. -For a good explanation on how the fork choice itself works, see the [annotated fork choice spec](https://github.com/ethereum/annotated-spec/blob/master/phase0/fork-choice.md). This is an annotated version of the [Ethereum Consensus fork choice spec](https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md) which `lodestar-fork-choice` is based on. +For a good explanation on how the fork choice itself works, see the [annotated fork choice spec](https://github.com/ethereum/annotated-spec/blob/master/phase0/fork-choice.md). This is an annotated version of the [Ethereum Consensus fork choice spec](https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/fork-choice.md) which `@lodestar/fork-choice` is based on. + +### `@lodestar/light-client` + +[@lodestar/light-client](https://github.com/ChainSafe/lodestar/tree/unstable/packages/light-client) is our light client designed to interact with the Ethereum blockchain in a trust-minimized matter via the sync committee and the [light-client protocol](https://github.com/ethereum/consensus-specs/tree/v1.4.0/specs/altair/light-client). + +### `@lodestar/logger` + +[@lodestar/logger](https://github.com/ChainSafe/lodestar/tree/unstable/packages/logger) is a common NodeJS logger for Lodestar binaries, which is required for worker threads to instantiate new loggers with consistent settings. + +### `@lodestar/params` + +[@lodestar/params](https://github.com/ChainSafe/lodestar/tree/unstable/packages/params) contains the parameters for configuring an Ethereum Consensus network. For example, the [mainnet params](https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#configuration). + +### `@lodestar/prover` + +[@lodestar/prover](https://github.com/ChainSafe/lodestar/tree/unstable/packages/prover) is a web3 provider and a proxy to enable verification of JSON-RPC calls to the execution client using the [light-client protocol](https://github.com/ethereum/consensus-specs/tree/v1.4.0/specs/altair/light-client). + +### `@lodestar/reqresp` + +[@lodestar/reqresp](https://github.com/ChainSafe/lodestar/tree/unstable/packages/reqresp) contains the TypeScript implementation of the [Ethereum Consensus Req/Resp protocol](https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/p2p-interface.md#reqresp). + +### `@lodestar/spec-test-util` + +[@lodestar/spec-test-util](https://github.com/ChainSafe/lodestar/tree/unstable/packages/spec-test-util) is a Vitest test utility harness used for adhering to the [Ethereum Consensus specification tests](https://github.com/ethereum/consensus-specs/tree/v1.4.0/tests). + +### `@lodestar/state-transition` -## `@lodestar/validator` +[@lodestar/state-transition](https://github.com/ChainSafe/lodestar/tree/unstable/packages/state-transition) contains the Lodestar implementation of the [beacon state transition function](https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/beacon-chain.md#beacon-chain-state-transition-function), which is used by `@lodestar/beacon-node` to perform the actual beacon state transition. This package also contains various functions used to calculate info about the beacon chain (such as `computeEpochAtSlot`) which are used by `@lodestar/fork-choice` and `@lodestar/validator` + +### `@lodestar/types` + +[@lodestar/types](https://github.com/ChainSafe/lodestar/tree/unstable/packages/types) contains Ethereum Consensus ssz types and data structures. + +### `@lodestar/utils` + +[@lodestar/utils](https://github.com/ChainSafe/lodestar/tree/unstable/packages/utils) contains various utilities that are common among the various Lodestar monorepo packages. + +### `@lodestar/validator` [@lodestar/validator](https://github.com/ChainSafe/lodestar/tree/unstable/packages/validator) contains the validator client. The sole consumer of this package is `@chainsafe/lodestar`, which provides CLI access to run and configure the validator client. However, the validator client communicates to a REST API that is contained in `@lodestar/beacon-node` (specifically in the `api` module) to perform the validator duties. -## `@lodestar/beacon-node` +--- -[@lodestar/beacon-node](https://github.com/ChainSafe/lodestar/tree/unstable/packages/beacon-node) contains the actual beacon node process itself, which is the aggregate of all the above packages and the "brain" of the Lodestar beacon chain implementation. All of the node modules live in this package as well. +## External Dependencies -## `@chainsafe/lodestar` +Below is a brief summary, listed alphabetically, of each of our main external dependencies managed externally from our monorepo. -[@chainsafe/lodestar](https://github.com/ChainSafe/lodestar/tree/unstable/packages/cli) combines everything together for CLI usage and configuration of the beacon node and validator. +### `@chainsafe/blst-ts` + +[@chainsafe/blst-ts`](https://github.com/ChainSafe/blst-ts) is our TypeScript wrapper for [@supranational/blst](https://github.com/supranational/blst) native bindings, a highly performant BLS12-381 signature library. + +### `@chainsafe/discv5` + +[@chainsafe/discv5](https://github.com/ChainSafe/discv5) is our monorepo containing our TypeScript implementation of the [discv5 Node Discovery Protocol v5](https://github.com/ethereum/devp2p/blob/master/discv5/discv5.md). + +### `@chainsafe/js-libp2p-gossipsub` + +[@chainsafe/js-libp2p-gossipsub](https://github.com/ChainSafe/js-libp2p-gossipsub) is an implementation of pubsub based on mmeshsub and floodsub. Specified under [@libp2p/specs/pubsub/gossipsub](https://github.com/libp2p/specs/tree/master/pubsub/gossipsub). + +### `@chainsafe/js-libp2p-noise` + +[@chainsafe/js-libp2p-noise](https://github.com/ChainSafe/js-libp2p-noise) contains the TypeScript implementation of the Noise protocol, an encryption protocol used in [@libp2p/specs/noise](https://github.com/libp2p/specs/blob/master/noise). + +### `@chainsafe/js-libp2p-yamux` + +[@chainsafe/js-libp2p-yamux](https://github.com/ChainSafe/js-libp2p-yamux) contains the JavaScript implementation of the [Yamux multiplexer from Hashicorp](https://github.com/hashicorp/yamux/blob/master/spec.md) designed for usage with js-libp2p. + +### `@chainsafe/ssz` + +[@chainsafe/ssz](https://github.com/ChainSafe/ssz) contains the packages as a monorepo related to the [Simple Serialize](https://github.com/ethereum/consensus-specs/blob/v1.4.0/ssz/simple-serialize.md). + +### `@libp2p/js-libp2p` + +[@libp2p/js-libp2p](https://github.com/libp2p/js-libp2p) is the JavaScript implementation of the libp2p networking stack used in Ethereum's networking stack. diff --git a/docs/pages/run/beacon-management/networking.md b/docs/pages/run/beacon-management/networking.md index 75f463a9b8f7..aaa202dc0af3 100644 --- a/docs/pages/run/beacon-management/networking.md +++ b/docs/pages/run/beacon-management/networking.md @@ -51,9 +51,9 @@ Alternatively, the ENR can also be retrieved from the beacon node API by queryin [ENR Viewer](https://enr-viewer.com/) provides a simple and convenient option to decode and inspect ENRs. -## Peer Communication (gossipsub and ReqResp) +## Peer Communication (gossipsub and Req/Resp) -Gossipsub and ReqResp are the two mechanisms that beacon nodes use to exchange chain data. Gossipsub is used disseminate the most recent relevant data proactively throughout the network. ReqResp is used to directly ask specific peers for specific information (eg: during syncing). +Gossipsub and Req/Resp are the two mechanisms that beacon nodes use to exchange chain data. Gossipsub is used disseminate the most recent relevant data proactively throughout the network. Req/Resp is used to directly ask specific peers for specific information (eg: during syncing). ### Gossipsub @@ -63,13 +63,13 @@ In GossipSub, nodes can subscribe to topics, effectively joining the correspondi Messages are propagated through a blend of eager-push and lazy-pull models. Specifically, the protocol employs "mesh links" to carry full messages actively and "gossip links" to carry only message identifiers (lazy-pull propagation model). This hybrid approach allows for both active message propagation and reactive message retrieval​ which is an extension of the traditional hub-and-spoke pub/sub model. -### ReqResp +### Req/Resp -ReqResp is the domain of protocols that establish a flexible, on-demand mechanism to retrieve historical data and data missed by gossip. This family of methods, implemented as separate libp2p protocols, operate between a single requester and responder. A method is initiated via a libp2p protocol ID, with the initiator sending a request message and the responder sending a response message. Every method defines a specific request and response message type, and a specific protocol ID. This framework also facilitates streaming responses and robust error handling. +Req/Resp is the domain of protocols that establish a flexible, on-demand mechanism to retrieve historical data and data missed by gossip. This family of methods, implemented as separate libp2p protocols, operate between a single requester and responder. A method is initiated via a libp2p protocol ID, with the initiator sending a request message and the responder sending a response message. Every method defines a specific request and response message type, and a specific protocol ID. This framework also facilitates streaming responses and robust error handling. ## Data Transport (libp2p) -Libp2p is a modular and extensible network stack that serves as the data transport layer below both gossipsub and ReqResp and facilitates the lower-level peer-to-peer communications. It provides a suite of protocols for various networking functionalities including network transports, connection encryption and protocol multiplexing. Its modular design allows for the easy addition, replacement, or upgrading of protocols, ensuring an adaptable and evolving networking stack. +Libp2p is a modular and extensible network stack that serves as the data transport layer below both gossipsub and Req/Resp and facilitates the lower-level peer-to-peer communications. It provides a suite of protocols for various networking functionalities including network transports, connection encryption and protocol multiplexing. Its modular design allows for the easy addition, replacement, or upgrading of protocols, ensuring an adaptable and evolving networking stack. Libp2p operates at the lower levels of the OSI model, particularly at the Transport and Network layers. Libp2p supports both TCP and UDP protocols for establishing connections and data transmission. Combined with libp2p's modular design it can integrate with various networking technologies to facilitating both routing and addressing. diff --git a/docs/pages/supporting-libraries/libraries.md b/docs/pages/supporting-libraries/libraries.md index e1576ba542f3..e76ccc2f9ec7 100644 --- a/docs/pages/supporting-libraries/libraries.md +++ b/docs/pages/supporting-libraries/libraries.md @@ -11,6 +11,8 @@ Several useful Ethereum consensus libraries are developed as part of the [Lodest - [`config`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/config) - Ethereum consensus run-time network configuration - [`api`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/api) - Ethereum consensus REST API client - [`flare`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/flare) - Beacon chain multi-purpose and debugging tool +- [`light-client`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/light-client) - Ethereum light client +- [`prover`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/prover) - A set of tools allowing to verify EL client JSON-RPC calls ## Other libraries diff --git a/docs/sidebars.ts b/docs/sidebars.ts index 16bd9586d1a1..d9a4839a90c4 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -53,12 +53,15 @@ const sidebars: SidebarsConfig = { { type: "category", label: "Light Client", - items: ["libraries/lightclient-prover/lightclient-cli", "libraries/lightclient-prover/lightclient"], + items: [ + {type: "doc", label: "Getting Started", id: "libraries/lightclient-prover/lightclient"}, + "libraries/lightclient-prover/lightclient-cli", + ], }, { type: "category", label: "Prover", - items: ["libraries/lightclient-prover/prover"], + items: [{type: "doc", label: "Getting Started", id: "libraries/lightclient-prover/prover"}], }, ], }, diff --git a/lerna.json b/lerna.json index ccdcaca872ec..043a5c48569a 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useNx": true, - "version": "1.20.2", + "version": "1.21.0", "stream": true, "command": { "version": { diff --git a/package.json b/package.json index 87ccd7e9f031..06fac0de8891 100644 --- a/package.json +++ b/package.json @@ -54,8 +54,8 @@ "@types/node": "^20.12.8", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", - "@vitest/browser": "^1.6.0", - "@vitest/coverage-v8": "^1.6.0", + "@vitest/browser": "^2.0.4", + "@vitest/coverage-v8": "^2.0.4", "crypto-browserify": "^3.12.0", "dotenv": "^16.4.5", "electron": "^26.2.2", @@ -81,12 +81,12 @@ "ts-node": "^10.9.2", "typescript": "^5.4.2", "typescript-docs-verifier": "^2.5.0", - "vite": "^5.2.11", + "vite": "^5.3.4", "vite-plugin-dts": "^3.9.1", - "vite-plugin-node-polyfills": "^0.21.0", - "vite-plugin-top-level-await": "^1.4.1", - "vitest": "^1.6.0", - "vitest-when": "^0.3.1", + "vite-plugin-node-polyfills": "^0.22.0", + "vite-plugin-top-level-await": "^1.4.2", + "vitest": "^2.0.4", + "vitest-when": "^0.4.1", "wait-port": "^1.1.0", "webdriverio": "^8.36.1" }, @@ -94,7 +94,7 @@ "@puppeteer/browsers": "^2.1.0", "dns-over-http-resolver": "^2.1.1", "loupe": "^2.3.6", - "vite": "^5.2.11", + "vite": "^5.3.4", "testcontainers/**/nan": "^2.19.0" } } diff --git a/packages/api/package.json b/packages/api/package.json index 5b91b46c128e..9a250fdc491c 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.20.2", + "version": "1.21.0", "type": "module", "exports": { ".": { @@ -72,10 +72,10 @@ "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.7.1", "@chainsafe/ssz": "^0.15.1", - "@lodestar/config": "^1.20.2", - "@lodestar/params": "^1.20.2", - "@lodestar/types": "^1.20.2", - "@lodestar/utils": "^1.20.2", + "@lodestar/config": "^1.21.0", + "@lodestar/params": "^1.21.0", + "@lodestar/types": "^1.21.0", + "@lodestar/utils": "^1.21.0", "eventsource": "^2.0.2", "qs": "^6.11.1" }, diff --git a/packages/api/src/beacon/client/events.ts b/packages/api/src/beacon/client/events.ts index 35383083ee44..2d63925a738a 100644 --- a/packages/api/src/beacon/client/events.ts +++ b/packages/api/src/beacon/client/events.ts @@ -44,7 +44,7 @@ export function getClient(config: ChainForkConfig, baseUrl: string): ApiClient { const errEs = err as unknown as EventSourceError; // Ignore noisy errors due to beacon node being offline - if (!errEs.message?.includes("ECONNREFUSED")) { + if (!/ECONNREFUSED|EAI_AGAIN/.test(errEs.message ?? "")) { // If there is no message it likely indicates that the server closed the connection onError?.(new Error(errEs.message ?? "Server closed connection")); } diff --git a/packages/api/src/beacon/routes/beacon/block.ts b/packages/api/src/beacon/routes/beacon/block.ts index 73680ac0afc2..99fbf7d45645 100644 --- a/packages/api/src/beacon/routes/beacon/block.ts +++ b/packages/api/src/beacon/routes/beacon/block.ts @@ -11,8 +11,10 @@ import { BeaconBlockBody, SignedBeaconBlockOrContents, SignedBlindedBeaconBlock, + SignedBlockContents, + sszTypesFor, } from "@lodestar/types"; -import {ForkName, ForkPreExecution, ForkSeq, isForkExecution} from "@lodestar/params"; +import {ForkName, ForkPreExecution, isForkBlobs, isForkExecution} from "@lodestar/params"; import {Endpoint, RequestCodec, RouteDefinitions, Schema} from "../../../utils/index.js"; import {EmptyMeta, EmptyResponseCodec, EmptyResponseData, WithVersion} from "../../../utils/codecs.js"; import { @@ -37,19 +39,10 @@ export const BlockHeadersResponseType = new ListCompositeType(BlockHeaderRespons export const RootResponseType = new ContainerType({ root: ssz.Root, }); -export const SignedBlockContentsType = new ContainerType( - { - signedBlock: ssz.deneb.SignedBeaconBlock, - kzgProofs: ssz.deneb.KZGProofs, - blobs: ssz.deneb.Blobs, - }, - {jsonCase: "eth2"} -); export type BlockHeaderResponse = ValueOf; export type BlockHeadersResponse = ValueOf; export type RootResponse = ValueOf; -export type SignedBlockContents = ValueOf; export type BlockId = RootHex | Slot | "head" | "genesis" | "finalized" | "justified"; @@ -297,11 +290,12 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const slot = isSignedBlockContents(signedBlockOrContents) ? signedBlockOrContents.signedBlock.message.slot : signedBlockOrContents.message.slot; + const fork = config.getForkName(slot); + return { - body: - config.getForkSeq(slot) < ForkSeq.deneb - ? config.getForkTypes(slot).SignedBeaconBlock.serialize(signedBlockOrContents as SignedBeaconBlock) - : SignedBlockContentsType.serialize(signedBlockOrContents as SignedBlockContents), + body: isForkBlobs(fork) + ? sszTypesFor(fork).SignedBlockContents.serialize(signedBlockOrContents as SignedBlockContents) + : sszTypesFor(fork).SignedBeaconBlock.serialize(signedBlockOrContents as SignedBeaconBlock), headers: { [MetaHeader.Version]: config.getForkName(slot), }, @@ -345,12 +338,10 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const forkName = toForkName(fromHeaders(headers, MetaHeader.Version)); - const forkSeq = config.forks[forkName].seq; return { - signedBlockOrContents: - forkSeq < ForkSeq.deneb - ? ssz[forkName].SignedBeaconBlock.deserialize(body) - : SignedBlockContentsType.deserialize(body), + signedBlockOrContents: isForkBlobs(forkName) + ? sszTypesFor(forkName).SignedBlockContents.deserialize(body) + : ssz[forkName].SignedBeaconBlock.deserialize(body), }; }, schema: { @@ -371,25 +362,23 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const forkName = toForkName(fromHeaders(headers, MetaHeader.Version)); - const forkSeq = config.forks[forkName].seq; return { - signedBlockOrContents: - forkSeq < ForkSeq.deneb - ? ssz[forkName].SignedBeaconBlock.fromJson(body) - : SignedBlockContentsType.fromJson(body), + signedBlockOrContents: isForkBlobs(forkName) + ? sszTypesFor(forkName).SignedBlockContents.fromJson(body) + : ssz[forkName].SignedBeaconBlock.fromJson(body), broadcastValidation: query.broadcast_validation as BroadcastValidation, }; }, @@ -397,25 +386,24 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions { const forkName = toForkName(fromHeaders(headers, MetaHeader.Version)); - const forkSeq = config.forks[forkName].seq; return { - signedBlockOrContents: - forkSeq < ForkSeq.deneb - ? ssz[forkName].SignedBeaconBlock.deserialize(body) - : SignedBlockContentsType.deserialize(body), + signedBlockOrContents: isForkBlobs(forkName) + ? sszTypesFor(forkName).SignedBlockContents.deserialize(body) + : ssz[forkName].SignedBeaconBlock.deserialize(body), broadcastValidation: query.broadcast_validation as BroadcastValidation, }; }, diff --git a/packages/api/src/beacon/routes/node.ts b/packages/api/src/beacon/routes/node.ts index 1ff0378c3330..0744b5f07452 100644 --- a/packages/api/src/beacon/routes/node.ts +++ b/packages/api/src/beacon/routes/node.ts @@ -43,6 +43,22 @@ export const PeerCountType = new ContainerType( {jsonCase: "eth2"} ); +export const SyncingStatusType = new ContainerType( + { + /** Head slot node is trying to reach */ + headSlot: ssz.Slot, + /** How many slots node needs to process to reach head. 0 if synced. */ + syncDistance: ssz.Slot, + /** Set to true if the node is syncing, false if the node is synced. */ + isSyncing: ssz.Boolean, + /** Set to true if the node is optimistically tracking head. */ + isOptimistic: ssz.Boolean, + /** Set to true if the connected el client is offline */ + elOffline: ssz.Boolean, + }, + {jsonCase: "eth2"} +); + export type NetworkIdentity = ValueOf; export type PeerState = "disconnected" | "connecting" | "connected" | "disconnecting"; @@ -66,18 +82,7 @@ export type FilterGetPeers = { direction?: PeerDirection[]; }; -export type SyncingStatus = { - /** Head slot node is trying to reach */ - headSlot: string; - /** How many slots node needs to process to reach head. 0 if synced. */ - syncDistance: string; - /** Set to true if the node is syncing, false if the node is synced. */ - isSyncing: boolean; - /** Set to true if the node is optimistically tracking head. */ - isOptimistic: boolean; - /** Set to true if the connected el client is offline */ - elOffline: boolean; -}; +export type SyncingStatus = ValueOf; export enum NodeHealth { READY = HttpStatusCode.OK, @@ -243,7 +248,10 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions, { params: {slot: number}; query: { randao_reveal: string; - graffiti: string; + graffiti?: string; fee_recipient?: string; builder_selection?: string; strict_fee_recipient_check?: boolean; @@ -333,7 +333,7 @@ export type Endpoints = { /** The validator's randao reveal value */ randaoReveal: BLSSignature; /** Arbitrary data validator wants to include in block */ - graffiti: string; + graffiti?: string; skipRandaoVerification?: boolean; builderBoostFactor?: UintBn64; } & ExtraProduceBlockOpts, @@ -341,7 +341,7 @@ export type Endpoints = { params: {slot: number}; query: { randao_reveal: string; - graffiti: string; + graffiti?: string; skip_randao_verification?: string; fee_recipient?: string; builder_selection?: string; @@ -359,9 +359,9 @@ export type Endpoints = { { slot: Slot; randaoReveal: BLSSignature; - graffiti: string; + graffiti?: string; }, - {params: {slot: number}; query: {randao_reveal: string; graffiti: string}}, + {params: {slot: number}; query: {randao_reveal: string; graffiti?: string}}, BlindedBeaconBlock, VersionMeta >; diff --git a/packages/api/src/utils/client/httpClient.ts b/packages/api/src/utils/client/httpClient.ts index 27757aa0846c..e11e35dc85c7 100644 --- a/packages/api/src/utils/client/httpClient.ts +++ b/packages/api/src/utils/client/httpClient.ts @@ -1,4 +1,4 @@ -import {ErrorAborted, Logger, MapDef, TimeoutError, isValidHttpUrl, retry} from "@lodestar/utils"; +import {ErrorAborted, Logger, MapDef, TimeoutError, isValidHttpUrl, retry, toPrintableUrl} from "@lodestar/utils"; import {mergeHeaders} from "../headers.js"; import {Endpoint} from "../types.js"; import {WireFormat} from "../wireFormat.js"; @@ -115,7 +115,12 @@ export class HttpClient implements IHttpClient { } // De-duplicate by baseUrl, having two baseUrls with different token or timeouts does not make sense if (!this.urlsInits.some((opt) => opt.baseUrl === urlInit.baseUrl)) { - this.urlsInits.push({...urlInit, urlIndex: i} as UrlInitRequired); + this.urlsInits.push({ + ...urlInit, + baseUrl: urlInit.baseUrl, + urlIndex: i, + printableUrl: toPrintableUrl(urlInit.baseUrl), + }); } } @@ -134,7 +139,7 @@ export class HttpClient implements IHttpClient { if (metrics) { metrics.urlsScore.addCollect(() => { for (let i = 0; i < this.urlsScore.length; i++) { - metrics.urlsScore.set({urlIndex: i, baseUrl: this.urlsInits[i].baseUrl}, this.urlsScore[i]); + metrics.urlsScore.set({urlIndex: i, baseUrl: this.urlsInits[i].printableUrl}, this.urlsScore[i]); } }); } @@ -185,12 +190,12 @@ export class HttpClient implements IHttpClient { // - If url[0] is good, only send to 0 // - If url[0] has recently errored, send to both 0, 1, etc until url[0] does not error for some time for (; i < this.urlsInits.length; i++) { - const baseUrl = this.urlsInits[i].baseUrl; + const {printableUrl} = this.urlsInits[i]; const routeId = definition.operationId; if (i > 0) { - this.metrics?.requestToFallbacks.inc({routeId, baseUrl}); - this.logger?.debug("Requesting fallback URL", {routeId, baseUrl, score: this.urlsScore[i]}); + this.metrics?.requestToFallbacks.inc({routeId, baseUrl: printableUrl}); + this.logger?.debug("Requesting fallback URL", {routeId, baseUrl: printableUrl, score: this.urlsScore[i]}); } // eslint-disable-next-line @typescript-eslint/naming-convention @@ -217,7 +222,11 @@ export class HttpClient implements IHttpClient { if (++errorCount >= requestCount) { resolve(res); } else { - this.logger?.debug("Request error, retrying", {routeId, baseUrl}, res.error() as Error); + this.logger?.debug( + "Request error, retrying", + {routeId, baseUrl: printableUrl}, + res.error() as Error + ); } } }, @@ -229,7 +238,7 @@ export class HttpClient implements IHttpClient { if (++errorCount >= requestCount) { reject(err); } else { - this.logger?.debug("Request error, retrying", {routeId, baseUrl}, err); + this.logger?.debug("Request error, retrying", {routeId, baseUrl: printableUrl}, err); } } ); @@ -347,7 +356,7 @@ export class HttpClient implements IHttpClient { abortSignals.forEach((s) => s?.addEventListener("abort", onSignalAbort)); const routeId = definition.operationId; - const {baseUrl, requestWireFormat, responseWireFormat} = init; + const {printableUrl, requestWireFormat, responseWireFormat} = init; const timer = this.metrics?.requestTime.startTimer({routeId}); try { @@ -359,7 +368,7 @@ export class HttpClient implements IHttpClient { if (!apiResponse.ok) { await apiResponse.errorBody(); this.logger?.debug("API response error", {routeId, status: apiResponse.status}); - this.metrics?.requestErrors.inc({routeId, baseUrl}); + this.metrics?.requestErrors.inc({routeId, baseUrl: printableUrl}); return apiResponse; } @@ -376,7 +385,7 @@ export class HttpClient implements IHttpClient { streamTimer?.(); } } catch (e) { - this.metrics?.requestErrors.inc({routeId, baseUrl}); + this.metrics?.requestErrors.inc({routeId, baseUrl: printableUrl}); if (isAbortedError(e)) { if (abortSignals.some((s) => s?.aborted)) { diff --git a/packages/api/src/utils/client/request.ts b/packages/api/src/utils/client/request.ts index 9c36ad111972..2040f1bbc5a9 100644 --- a/packages/api/src/utils/client/request.ts +++ b/packages/api/src/utils/client/request.ts @@ -28,7 +28,12 @@ export type OptionalRequestInit = { }; export type UrlInit = ApiRequestInit & {baseUrl?: string}; -export type UrlInitRequired = ApiRequestInit & {urlIndex: number; baseUrl: string}; +export type UrlInitRequired = ApiRequestInit & { + urlIndex: number; + baseUrl: string; + /** Used in logs and metrics to prevent leaking user credentials */ + printableUrl: string; +}; export type ApiRequestInit = ExtraRequestInit & OptionalRequestInit & RequestInit; export type ApiRequestInitRequired = Required & UrlInitRequired; diff --git a/packages/api/src/utils/serdes.ts b/packages/api/src/utils/serdes.ts index 73196c917a66..233d7db9e7f8 100644 --- a/packages/api/src/utils/serdes.ts +++ b/packages/api/src/utils/serdes.ts @@ -77,7 +77,11 @@ export function fromValidatorIdsStr(ids?: string[]): (string | number)[] | undef const GRAFFITI_HEX_LENGTH = 66; -export function toGraffitiHex(utf8: string): string { +export function toGraffitiHex(utf8?: string): string | undefined { + if (utf8 === undefined) { + return undefined; + } + const hex = toHexString(new TextEncoder().encode(utf8)); if (hex.length > GRAFFITI_HEX_LENGTH) { @@ -93,7 +97,10 @@ export function toGraffitiHex(utf8: string): string { return hex; } -export function fromGraffitiHex(hex: string): string { +export function fromGraffitiHex(hex?: string): string | undefined { + if (hex === undefined) { + return undefined; + } try { return new TextDecoder("utf8").decode(fromHexString(hex)); } catch { diff --git a/packages/api/test/unit/beacon/testData/node.ts b/packages/api/test/unit/beacon/testData/node.ts index 48efc4a728bc..e46aa3e28850 100644 --- a/packages/api/test/unit/beacon/testData/node.ts +++ b/packages/api/test/unit/beacon/testData/node.ts @@ -49,7 +49,7 @@ export const testData: GenericServerTestCases = { }, getSyncingStatus: { args: undefined, - res: {data: {headSlot: "1", syncDistance: "2", isSyncing: false, isOptimistic: true, elOffline: false}}, + res: {data: {headSlot: 1, syncDistance: 2, isSyncing: false, isOptimistic: true, elOffline: false}}, }, getHealth: { args: {syncingStatus: 206}, diff --git a/packages/api/test/unit/client/httpClientFallback.test.ts b/packages/api/test/unit/client/httpClientFallback.test.ts index 10f4a60c678a..ea20c3f33982 100644 --- a/packages/api/test/unit/client/httpClientFallback.test.ts +++ b/packages/api/test/unit/client/httpClientFallback.test.ts @@ -17,7 +17,7 @@ describe("httpClient fallback", () => { // Using fetchSub instead of actually setting up servers because there are some strange // race conditions, where the server stub doesn't count the call in time before the test is over. - const fetchStub = vi.fn, ReturnType>(); + const fetchStub = vi.fn<(...args: Parameters) => ReturnType>(); let httpClient: HttpClient; diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 80d61cb119cb..8d0189c710bf 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.20.2", + "version": "1.21.0", "type": "module", "exports": { ".": { @@ -78,10 +78,10 @@ "lint": "eslint --color --ext .ts src/ test/", "lint:fix": "yarn run lint --fix", "test": "yarn test:unit && yarn test:e2e", - "test:unit:minimal": "LODESTAR_PRESET=minimal vitest --run --segfaultRetry 3 --dir test/unit/", - "test:unit:mainnet": "LODESTAR_PRESET=mainnet vitest --run --segfaultRetry 3 --dir test/unit-mainnet", + "test:unit:minimal": "LODESTAR_PRESET=minimal vitest --run --dir test/unit/", + "test:unit:mainnet": "LODESTAR_PRESET=mainnet vitest --run --dir test/unit-mainnet", "test:unit": "wrapper() { yarn test:unit:minimal $@ && yarn test:unit:mainnet $@; }; wrapper", - "test:e2e": "LODESTAR_PRESET=minimal vitest --run --segfaultRetry 3 --config vitest.e2e.config.ts --dir test/e2e", + "test:e2e": "LODESTAR_PRESET=minimal vitest --run --config vitest.e2e.config.ts --dir test/e2e", "test:sim": "vitest --run test/sim/**/*.test.ts", "test:sim:mergemock": "vitest --run test/sim/mergemock.test.ts", "test:sim:blobs": "vitest --run test/sim/4844-interop.test.ts", @@ -95,8 +95,7 @@ }, "dependencies": { "@chainsafe/as-sha256": "^0.4.1", - "@chainsafe/bls": "7.1.3", - "@chainsafe/blst": "^0.2.11", + "@chainsafe/blst": "^2.0.3", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/libp2p-gossipsub": "^13.0.0", @@ -120,18 +119,18 @@ "@libp2p/peer-id-factory": "^4.1.0", "@libp2p/prometheus-metrics": "^3.0.21", "@libp2p/tcp": "9.0.23", - "@lodestar/api": "^1.20.2", - "@lodestar/config": "^1.20.2", - "@lodestar/db": "^1.20.2", - "@lodestar/fork-choice": "^1.20.2", - "@lodestar/light-client": "^1.20.2", - "@lodestar/logger": "^1.20.2", - "@lodestar/params": "^1.20.2", - "@lodestar/reqresp": "^1.20.2", - "@lodestar/state-transition": "^1.20.2", - "@lodestar/types": "^1.20.2", - "@lodestar/utils": "^1.20.2", - "@lodestar/validator": "^1.20.2", + "@lodestar/api": "^1.21.0", + "@lodestar/config": "^1.21.0", + "@lodestar/db": "^1.21.0", + "@lodestar/fork-choice": "^1.21.0", + "@lodestar/light-client": "^1.21.0", + "@lodestar/logger": "^1.21.0", + "@lodestar/params": "^1.21.0", + "@lodestar/reqresp": "^1.21.0", + "@lodestar/state-transition": "^1.21.0", + "@lodestar/types": "^1.21.0", + "@lodestar/utils": "^1.21.0", + "@lodestar/validator": "^1.21.0", "@multiformats/multiaddr": "^12.1.3", "c-kzg": "^2.1.2", "datastore-core": "^9.1.1", diff --git a/packages/beacon-node/src/api/impl/api.ts b/packages/beacon-node/src/api/impl/api.ts index 6ec7180cf0f4..3647feed2d7c 100644 --- a/packages/beacon-node/src/api/impl/api.ts +++ b/packages/beacon-node/src/api/impl/api.ts @@ -21,6 +21,6 @@ export function getApi(opts: ApiOptions, modules: ApiModules): BeaconApiMethods lodestar: getLodestarApi(modules), node: getNodeApi(opts, modules), proof: getProofApi(opts, modules), - validator: getValidatorApi(modules), + validator: getValidatorApi(opts, modules), }; } diff --git a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts index 177f58aebb95..1b8a59cc8967 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/index.ts @@ -34,7 +34,7 @@ import {ApiModules} from "../../types.js"; import {validateGossipBlock} from "../../../../chain/validation/block.js"; import {verifyBlocksInEpoch} from "../../../../chain/blocks/verifyBlock.js"; import {BeaconChain} from "../../../../chain/chain.js"; -import {resolveBlockId, toBeaconHeaderResponse} from "./utils.js"; +import {getBlockResponse, toBeaconHeaderResponse} from "./utils.js"; type PublishBlockOpts = ImportBlockOpts; @@ -371,7 +371,7 @@ export function getBeaconBlockApi({ }, async getBlockHeader({blockId}) { - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); return { data: toBeaconHeaderResponse(config, block, true), meta: {executionOptimistic, finalized}, @@ -379,7 +379,7 @@ export function getBeaconBlockApi({ }, async getBlockV2({blockId}) { - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); return { data: block, meta: { @@ -391,7 +391,7 @@ export function getBeaconBlockApi({ }, async getBlindedBlock({blockId}) { - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); const fork = config.getForkName(block.message.slot); return { data: isForkExecution(fork) @@ -406,7 +406,7 @@ export function getBeaconBlockApi({ }, async getBlockAttestations({blockId}) { - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); return { data: Array.from(block.message.body.attestations), meta: {executionOptimistic, finalized}, @@ -445,7 +445,7 @@ export function getBeaconBlockApi({ } // Slow path - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); return { data: {root: config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message)}, meta: {executionOptimistic, finalized}, @@ -464,7 +464,7 @@ export function getBeaconBlockApi({ }, async getBlobSidecars({blockId, indices}) { - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); const blockRoot = config.getForkTypes(block.message.slot).BeaconBlock.hashTreeRoot(block.message); let {blobSidecars} = (await db.blobSidecars.get(blockRoot)) ?? {}; diff --git a/packages/beacon-node/src/api/impl/beacon/blocks/utils.ts b/packages/beacon-node/src/api/impl/beacon/blocks/utils.ts index f0d243967c22..fe4fc5ca3dc0 100644 --- a/packages/beacon-node/src/api/impl/beacon/blocks/utils.ts +++ b/packages/beacon-node/src/api/impl/beacon/blocks/utils.ts @@ -1,7 +1,8 @@ import {routes} from "@lodestar/api"; import {blockToHeader} from "@lodestar/state-transition"; import {ChainForkConfig} from "@lodestar/config"; -import {SignedBeaconBlock} from "@lodestar/types"; +import {RootHex, SignedBeaconBlock, Slot} from "@lodestar/types"; +import {IForkChoice} from "@lodestar/fork-choice"; import {GENESIS_SLOT} from "../../../../constants/index.js"; import {ApiError, ValidationError} from "../../errors.js"; import {IBeaconChain} from "../../../../chain/interface.js"; @@ -22,44 +23,29 @@ export function toBeaconHeaderResponse( }; } -export async function resolveBlockId( - chain: IBeaconChain, - blockId: routes.beacon.BlockId -): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean}> { - const res = await resolveBlockIdOrNull(chain, blockId); - if (!res) { - throw new ApiError(404, `No block found for id '${blockId}'`); - } - - return res; -} - -async function resolveBlockIdOrNull( - chain: IBeaconChain, - blockId: routes.beacon.BlockId -): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean} | null> { +export function resolveBlockId(forkChoice: IForkChoice, blockId: routes.beacon.BlockId): RootHex | Slot { blockId = String(blockId).toLowerCase(); if (blockId === "head") { - return chain.getBlockByRoot(chain.forkChoice.getHead().blockRoot); + return forkChoice.getHead().blockRoot; } if (blockId === "genesis") { - return chain.getCanonicalBlockAtSlot(GENESIS_SLOT); + return GENESIS_SLOT; } if (blockId === "finalized") { - return chain.getCanonicalBlockAtSlot(chain.forkChoice.getFinalizedBlock().slot); + return forkChoice.getFinalizedBlock().blockRoot; } if (blockId === "justified") { - return chain.getBlockByRoot(chain.forkChoice.getJustifiedBlock().blockRoot); + return forkChoice.getJustifiedBlock().blockRoot; } if (blockId.startsWith("0x")) { if (!rootHexRegex.test(blockId)) { throw new ValidationError(`Invalid block id '${blockId}'`, "blockId"); } - return chain.getBlockByRoot(blockId); + return blockId; } // block id must be slot @@ -67,5 +53,23 @@ async function resolveBlockIdOrNull( if (isNaN(blockSlot) && isNaN(blockSlot - 0)) { throw new ValidationError(`Invalid block id '${blockId}'`, "blockId"); } - return chain.getCanonicalBlockAtSlot(blockSlot); + return blockSlot; +} + +export async function getBlockResponse( + chain: IBeaconChain, + blockId: routes.beacon.BlockId +): Promise<{block: SignedBeaconBlock; executionOptimistic: boolean; finalized: boolean}> { + const rootOrSlot = resolveBlockId(chain.forkChoice, blockId); + + const res = + typeof rootOrSlot === "string" + ? await chain.getBlockByRoot(rootOrSlot) + : await chain.getCanonicalBlockAtSlot(rootOrSlot); + + if (!res) { + throw new ApiError(404, `No block found for id '${blockId}'`); + } + + return res; } diff --git a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts index 96399db27b4f..8d8a77701b91 100644 --- a/packages/beacon-node/src/api/impl/beacon/rewards/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/rewards/index.ts @@ -1,14 +1,14 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ApiModules} from "../../types.js"; -import {resolveBlockId} from "../blocks/utils.js"; +import {getBlockResponse} from "../blocks/utils.js"; export function getBeaconRewardsApi({ chain, }: Pick): ApplicationMethods { return { async getBlockRewards({blockId}) { - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); const data = await chain.getBlockRewards(block.message); return {data, meta: {executionOptimistic, finalized}}; }, @@ -17,7 +17,7 @@ export function getBeaconRewardsApi({ return {data: rewards, meta: {executionOptimistic, finalized}}; }, async getSyncCommitteeRewards({blockId, validatorIds}) { - const {block, executionOptimistic, finalized} = await resolveBlockId(chain, blockId); + const {block, executionOptimistic, finalized} = await getBlockResponse(chain, blockId); const data = await chain.getSyncCommitteeRewards(block.message, validatorIds); return {data, meta: {executionOptimistic, finalized}}; }, diff --git a/packages/beacon-node/src/api/impl/beacon/state/index.ts b/packages/beacon-node/src/api/impl/beacon/state/index.ts index 201cc875123e..9d9646ee8cf3 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/index.ts @@ -15,7 +15,7 @@ import { filterStateValidatorsByStatus, getStateValidatorIndex, getValidatorStatus, - resolveStateId, + getStateResponse, toValidatorResponse, } from "./utils.js"; @@ -26,7 +26,7 @@ export function getBeaconStateApi({ async function getState( stateId: routes.beacon.StateId ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean}> { - return resolveStateId(chain, stateId); + return getStateResponse(chain, stateId); } return { @@ -76,7 +76,7 @@ export function getBeaconStateApi({ }, async getStateValidators({stateId, validatorIds = [], statuses = []}) { - const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); + const {state, executionOptimistic, finalized} = await getStateResponse(chain, stateId); const currentEpoch = getCurrentEpoch(state); const {validators, balances} = state; // Get the validators sub tree once for all the loop const {pubkey2index} = chain.getHeadState().epochCtx; @@ -131,7 +131,7 @@ export function getBeaconStateApi({ }, async getStateValidator({stateId, validatorId}) { - const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); + const {state, executionOptimistic, finalized} = await getStateResponse(chain, stateId); const {pubkey2index} = chain.getHeadState().epochCtx; const resp = getStateValidatorIndex(validatorId, state, pubkey2index); @@ -152,7 +152,7 @@ export function getBeaconStateApi({ }, async getStateValidatorBalances({stateId, validatorIds = []}) { - const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); + const {state, executionOptimistic, finalized} = await getStateResponse(chain, stateId); if (validatorIds.length) { const headState = chain.getHeadState(); @@ -193,7 +193,7 @@ export function getBeaconStateApi({ }, async getEpochCommittees({stateId, ...filters}) { - const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); + const {state, executionOptimistic, finalized} = await getStateResponse(chain, stateId); const stateCached = state as CachedBeaconStateAltair; if (stateCached.epochCtx === undefined) { @@ -235,7 +235,7 @@ export function getBeaconStateApi({ */ async getEpochSyncCommittees({stateId, epoch}) { // TODO: Should pick a state with the provided epoch too - const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId); + const {state, executionOptimistic, finalized} = await getStateResponse(chain, stateId); // TODO: If possible compute the syncCommittees in advance of the fork and expose them here. // So the validators can prepare and potentially attest the first block. Not critical tho, it's very unlikely diff --git a/packages/beacon-node/src/api/impl/beacon/state/utils.ts b/packages/beacon-node/src/api/impl/beacon/state/utils.ts index 73f7134e1530..40b1e2815263 100644 --- a/packages/beacon-node/src/api/impl/beacon/state/utils.ts +++ b/packages/beacon-node/src/api/impl/beacon/state/utils.ts @@ -1,53 +1,34 @@ import {routes} from "@lodestar/api"; import {FAR_FUTURE_EPOCH, GENESIS_SLOT} from "@lodestar/params"; import {BeaconStateAllForks, PubkeyIndexMap} from "@lodestar/state-transition"; -import {BLSPubkey, Epoch, phase0, ValidatorIndex} from "@lodestar/types"; +import {BLSPubkey, Epoch, phase0, RootHex, Slot, ValidatorIndex} from "@lodestar/types"; import {fromHex} from "@lodestar/utils"; -import {IBeaconChain, StateGetOpts} from "../../../../chain/index.js"; +import {CheckpointWithHex, IForkChoice} from "@lodestar/fork-choice"; +import {IBeaconChain} from "../../../../chain/index.js"; import {ApiError, ValidationError} from "../../errors.js"; -import {isOptimisticBlock} from "../../../../util/forkChoice.js"; -export async function resolveStateId( - chain: IBeaconChain, - stateId: routes.beacon.StateId, - opts?: StateGetOpts -): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean}> { - const stateRes = await resolveStateIdOrNull(chain, stateId, opts); - if (!stateRes) { - throw new ApiError(404, `No state found for id '${stateId}'`); - } - - return stateRes; -} - -async function resolveStateIdOrNull( - chain: IBeaconChain, - stateId: routes.beacon.StateId, - opts?: StateGetOpts -): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null> { +export function resolveStateId( + forkChoice: IForkChoice, + stateId: routes.beacon.StateId +): RootHex | Slot | CheckpointWithHex { if (stateId === "head") { - // TODO: This is not OK, head and headState must be fetched atomically - const head = chain.forkChoice.getHead(); - const headState = chain.getHeadState(); - return {state: headState, executionOptimistic: isOptimisticBlock(head), finalized: false}; + return forkChoice.getHead().stateRoot; } if (stateId === "genesis") { - return chain.getStateBySlot(GENESIS_SLOT, opts); + return GENESIS_SLOT; } if (stateId === "finalized") { - const checkpoint = chain.forkChoice.getFinalizedCheckpoint(); - return chain.getStateByCheckpoint(checkpoint); + return forkChoice.getFinalizedCheckpoint(); } if (stateId === "justified") { - const checkpoint = chain.forkChoice.getJustifiedCheckpoint(); - return chain.getStateByCheckpoint(checkpoint); + return forkChoice.getJustifiedCheckpoint(); } if (typeof stateId === "string" && stateId.startsWith("0x")) { - return chain.getStateByStateRoot(stateId, opts); + return stateId; } // id must be slot @@ -56,7 +37,49 @@ async function resolveStateIdOrNull( throw new ValidationError(`Invalid block id '${stateId}'`, "blockId"); } - return chain.getStateBySlot(blockSlot, opts); + return blockSlot; +} + +export async function getStateResponse( + chain: IBeaconChain, + inStateId: routes.beacon.StateId +): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean}> { + const stateId = resolveStateId(chain.forkChoice, inStateId); + + const res = + typeof stateId === "string" + ? await chain.getStateByStateRoot(stateId) + : typeof stateId === "number" + ? await chain.getStateBySlot(stateId) + : chain.getStateByCheckpoint(stateId); + + if (!res) { + throw new ApiError(404, `No state found for id '${inStateId}'`); + } + + return res; +} + +export async function getStateResponseWithRegen( + chain: IBeaconChain, + inStateId: routes.beacon.StateId +): Promise<{state: BeaconStateAllForks | Uint8Array; executionOptimistic: boolean; finalized: boolean}> { + const stateId = resolveStateId(chain.forkChoice, inStateId); + + const res = + typeof stateId === "string" + ? await chain.getStateByStateRoot(stateId, {allowRegen: true}) + : typeof stateId === "number" + ? stateId >= chain.forkChoice.getFinalizedBlock().slot + ? await chain.getStateBySlot(stateId, {allowRegen: true}) + : await chain.getHistoricalStateBySlot(stateId) + : await chain.getStateOrBytesByCheckpoint(stateId); + + if (!res) { + throw new ApiError(404, `No state found for id '${inStateId}'`); + } + + return res; } /** diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index f1254ae1b7fb..4edb8ba9b2dd 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -1,8 +1,10 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; -import {resolveStateId} from "../beacon/state/utils.js"; +import {BeaconState} from "@lodestar/types"; +import {getStateResponseWithRegen} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; +import {getStateSlotFromBytes} from "../../../util/multifork.js"; export function getDebugApi({ chain, @@ -34,11 +36,19 @@ export function getDebugApi({ }, async getStateV2({stateId}, context) { - const {state, executionOptimistic, finalized} = await resolveStateId(chain, stateId, {allowRegen: true}); + const {state, executionOptimistic, finalized} = await getStateResponseWithRegen(chain, stateId); + let slot: number, data: Uint8Array | BeaconState; + if (state instanceof Uint8Array) { + slot = getStateSlotFromBytes(state); + data = state; + } else { + slot = state.slot; + data = context?.returnBytes ? state.serialize() : state.toValue(); + } return { - data: context?.returnBytes ? state.serialize() : state.toValue(), + data, meta: { - version: config.getForkName(state.slot), + version: config.getForkName(slot), executionOptimistic, finalized, }, diff --git a/packages/beacon-node/src/api/impl/lightclient/index.ts b/packages/beacon-node/src/api/impl/lightclient/index.ts index 13deb16a9cb0..0dd366accf68 100644 --- a/packages/beacon-node/src/api/impl/lightclient/index.ts +++ b/packages/beacon-node/src/api/impl/lightclient/index.ts @@ -3,7 +3,7 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {MAX_REQUEST_LIGHT_CLIENT_UPDATES, MAX_REQUEST_LIGHT_CLIENT_COMMITTEE_HASHES} from "@lodestar/params"; import {ApiModules} from "../types.js"; - +import {assertLightClientServer} from "../../../node/utils/lightclient.js"; // TODO: Import from lightclient/server package export function getLightclientApi({ @@ -12,9 +12,12 @@ export function getLightclientApi({ }: Pick): ApplicationMethods { return { async getLightClientUpdatesByRange({startPeriod, count}) { + const lightClientServer = chain.lightClientServer; + assertLightClientServer(lightClientServer); + const maxAllowedCount = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, count); const periods = Array.from({length: maxAllowedCount}, (_ignored, i) => i + startPeriod); - const updates = await Promise.all(periods.map((period) => chain.lightClientServer.getUpdate(period))); + const updates = await Promise.all(periods.map((period) => lightClientServer.getUpdate(period))); return { data: updates, meta: {versions: updates.map((update) => config.getForkName(update.attestedHeader.beacon.slot))}, @@ -22,6 +25,8 @@ export function getLightclientApi({ }, async getLightClientOptimisticUpdate() { + assertLightClientServer(chain.lightClientServer); + const update = chain.lightClientServer.getOptimisticUpdate(); if (update === null) { throw Error("No optimistic update available"); @@ -30,6 +35,8 @@ export function getLightclientApi({ }, async getLightClientFinalityUpdate() { + assertLightClientServer(chain.lightClientServer); + const update = chain.lightClientServer.getFinalityUpdate(); if (update === null) { throw Error("No finality update available"); @@ -38,16 +45,19 @@ export function getLightclientApi({ }, async getLightClientBootstrap({blockRoot}) { + assertLightClientServer(chain.lightClientServer); + const bootstrapProof = await chain.lightClientServer.getBootstrap(fromHex(blockRoot)); return {data: bootstrapProof, meta: {version: config.getForkName(bootstrapProof.header.beacon.slot)}}; }, async getLightClientCommitteeRoot({startPeriod, count}) { + const lightClientServer = chain.lightClientServer; + assertLightClientServer(lightClientServer); + const maxAllowedCount = Math.min(MAX_REQUEST_LIGHT_CLIENT_COMMITTEE_HASHES, count); const periods = Array.from({length: maxAllowedCount}, (_ignored, i) => i + startPeriod); - const committeeHashes = await Promise.all( - periods.map((period) => chain.lightClientServer.getCommitteeRoot(period)) - ); + const committeeHashes = await Promise.all(periods.map((period) => lightClientServer.getCommitteeRoot(period))); return {data: committeeHashes}; }, }; diff --git a/packages/beacon-node/src/api/impl/proof/index.ts b/packages/beacon-node/src/api/impl/proof/index.ts index a581fb8eed59..9e1a33940225 100644 --- a/packages/beacon-node/src/api/impl/proof/index.ts +++ b/packages/beacon-node/src/api/impl/proof/index.ts @@ -2,8 +2,8 @@ import {CompactMultiProof, createProof, ProofType} from "@chainsafe/persistent-m import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ApiModules} from "../types.js"; -import {resolveStateId} from "../beacon/state/utils.js"; -import {resolveBlockId} from "../beacon/blocks/utils.js"; +import {getStateResponse} from "../beacon/state/utils.js"; +import {getBlockResponse} from "../beacon/blocks/utils.js"; import {ApiOptions} from "../../options.js"; export function getProofApi( @@ -21,7 +21,7 @@ export function getProofApi( throw new Error("Requested proof is too large."); } - const {state} = await resolveStateId(chain, stateId); + const {state} = await getStateResponse(chain, stateId); // Commit any changes before computing the state root. In normal cases the state should have no changes here state.commit(); @@ -40,7 +40,7 @@ export function getProofApi( throw new Error("Requested proof is too large."); } - const {block} = await resolveBlockId(chain, blockId); + const {block} = await getBlockResponse(chain, blockId); // Commit any changes before computing the state root. In normal cases the state should have no changes here const blockNode = config.getForkTypes(block.message.slot).BeaconBlock.toView(block.message).node; diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index 486fc6e8062f..b2a0b8575f5c 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -53,7 +53,7 @@ import {validateApiAggregateAndProof} from "../../../chain/validation/index.js"; import {ZERO_HASH} from "../../../constants/index.js"; import {SyncState} from "../../../sync/index.js"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; -import {toGraffitiBuffer} from "../../../util/graffiti.js"; +import {getDefaultGraffiti, toGraffitiBuffer} from "../../../util/graffiti.js"; import {ApiError, NodeIsSyncing, OnlySupportedByDVT} from "../errors.js"; import {validateSyncCommitteeGossipContributionAndProof} from "../../../chain/validation/syncCommitteeContributionAndProof.js"; import {CommitteeSubscription} from "../../../network/subnets/index.js"; @@ -63,6 +63,8 @@ import {getValidatorStatus} from "../beacon/state/utils.js"; import {validateGossipFnRetryUnknownRoot} from "../../../network/processor/gossipHandlers.js"; import {SCHEDULER_LOOKAHEAD_FACTOR} from "../../../chain/prepareNextSlot.js"; import {ChainEvent, CheckpointHex, CommonBlockBody} from "../../../chain/index.js"; +import {ApiOptions} from "../../options.js"; +import {getLodestarClientVersion} from "../../../util/metadata.js"; import {computeSubnetForCommitteesAtSlot, getPubkeysForIndices, selectBlockProductionSource} from "./utils.js"; /** @@ -110,14 +112,10 @@ type ProduceFullOrBlindedBlockOrContentsRes = {executionPayloadSource: ProducedB * Server implementation for handling validator duties. * See `@lodestar/validator/src/api` for the client implementation). */ -export function getValidatorApi({ - chain, - config, - logger, - metrics, - network, - sync, -}: ApiModules): ApplicationMethods { +export function getValidatorApi( + opts: ApiOptions, + {chain, config, logger, metrics, network, sync}: ApiModules +): ApplicationMethods { let genesisBlockRoot: Root | null = null; /** @@ -348,7 +346,7 @@ export function getValidatorApi({ async function produceBuilderBlindedBlock( slot: Slot, randaoReveal: BLSSignature, - graffiti: string, + graffiti?: string, // as of now fee recipient checks can not be performed because builder does not return bid recipient { skipHeadChecksAndUpdate, @@ -406,7 +404,9 @@ export function getValidatorApi({ slot, parentBlockRoot, randaoReveal, - graffiti: toGraffitiBuffer(graffiti || ""), + graffiti: toGraffitiBuffer( + graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts) + ), commonBlockBody, }); @@ -432,7 +432,7 @@ export function getValidatorApi({ async function produceEngineFullBlockOrContents( slot: Slot, randaoReveal: BLSSignature, - graffiti: string, + graffiti?: string, { feeRecipient, strictFeeRecipientCheck, @@ -474,7 +474,9 @@ export function getValidatorApi({ slot, parentBlockRoot, randaoReveal, - graffiti: toGraffitiBuffer(graffiti || ""), + graffiti: toGraffitiBuffer( + graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts) + ), feeRecipient, commonBlockBody, }); @@ -522,7 +524,7 @@ export function getValidatorApi({ async function produceEngineOrBuilderBlock( slot: Slot, randaoReveal: BLSSignature, - graffiti: string, + graffiti?: string, // TODO deneb: skip randao verification _skipRandaoVerification?: boolean, builderBoostFactor?: bigint, @@ -585,7 +587,9 @@ export function getValidatorApi({ slot, parentBlockRoot, randaoReveal, - graffiti: toGraffitiBuffer(graffiti || ""), + graffiti: toGraffitiBuffer( + graffiti ?? getDefaultGraffiti(getLodestarClientVersion(opts), chain.executionEngine.clientVersion, opts) + ), }); logger.debug("Produced common block body", loggerContext); diff --git a/packages/beacon-node/src/api/options.ts b/packages/beacon-node/src/api/options.ts index 4b9aed7e0d94..811621ba97bf 100644 --- a/packages/beacon-node/src/api/options.ts +++ b/packages/beacon-node/src/api/options.ts @@ -3,11 +3,14 @@ import {beaconRestApiServerOpts, BeaconRestApiServerOpts} from "./rest/index.js" export type ApiOptions = { maxGindicesInProof?: number; rest: BeaconRestApiServerOpts; + commit?: string; version?: string; + private?: boolean; }; export const defaultApiOptions: ApiOptions = { maxGindicesInProof: 512, rest: beaconRestApiServerOpts, version: "dev", + private: false, }; diff --git a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts index 71fa9fb61b2e..76bfe651ad77 100644 --- a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts +++ b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts @@ -32,7 +32,7 @@ export async function archiveBlocks( config: ChainForkConfig, db: IBeaconDb, forkChoice: IForkChoice, - lightclientServer: LightClientServer, + lightclientServer: LightClientServer | undefined, logger: Logger, finalizedCheckpoint: CheckpointHex, currentEpoch: Epoch, @@ -111,7 +111,9 @@ export async function archiveBlocks( nonCheckpointBlockRoots.push(block.root); } - await lightclientServer.pruneNonCheckpointData(nonCheckpointBlockRoots); + if (lightclientServer) { + await lightclientServer.pruneNonCheckpointData(nonCheckpointBlockRoots); + } logger.verbose("Archiving of finalized blocks complete", { totalArchived: finalizedCanonicalBlocks.length, diff --git a/packages/beacon-node/src/chain/blocks/importBlock.ts b/packages/beacon-node/src/chain/blocks/importBlock.ts index e67b6d1e9dbc..9d46410a8638 100644 --- a/packages/beacon-node/src/chain/blocks/importBlock.ts +++ b/packages/beacon-node/src/chain/blocks/importBlock.ts @@ -101,34 +101,6 @@ export async function importBlock( this.metrics?.importBlock.bySource.inc({source}); this.logger.verbose("Added block to forkchoice and state cache", {slot: blockSlot, root: blockRootHex}); - // We want to import block asap so call all event handler in the next event loop - callInNextEventLoop(async () => { - this.emitter.emit(routes.events.EventType.block, { - block: blockRootHex, - slot: blockSlot, - executionOptimistic: blockSummary != null && isOptimisticBlock(blockSummary), - }); - - // dataPromise will not end up here, but preDeneb could. In future we might also allow syncing - // out of data range blocks and import then in forkchoice although one would not be able to - // attest and propose with such head similar to optimistic sync - if (blockInput.type === BlockInputType.availableData) { - const {blobsSource, blobs} = blockInput.blockData; - - this.metrics?.importBlock.blobsBySource.inc({blobsSource}); - for (const blobSidecar of blobs) { - const {index, kzgCommitment} = blobSidecar; - this.emitter.emit(routes.events.EventType.blobSidecar, { - blockRoot: blockRootHex, - slot: blockSlot, - index, - kzgCommitment: toHexString(kzgCommitment), - versionedHash: toHexString(kzgCommitmentToVersionedHash(kzgCommitment)), - }); - } - } - }); - // 3. Import attestations to fork choice // // - For each attestation @@ -241,7 +213,7 @@ export async function importBlock( if (newHead.blockRoot !== oldHead.blockRoot) { // Set head state as strong reference - this.regen.updateHeadState(newHead.stateRoot, postState); + this.regen.updateHeadState(newHead, postState); this.emitter.emit(routes.events.EventType.head, { block: newHead.blockRoot, @@ -305,7 +277,7 @@ export async function importBlock( // we want to import block asap so do this in the next event loop callInNextEventLoop(() => { try { - this.lightClientServer.onImportBlockHead( + this.lightClientServer?.onImportBlockHead( block.message as BeaconBlock, postState as CachedBeaconStateAltair, parentBlockSlot @@ -413,32 +385,58 @@ export async function importBlock( // Send block events, only for recent enough blocks if (this.clock.currentSlot - blockSlot < EVENTSTREAM_EMIT_RECENT_BLOCK_SLOTS) { - // NOTE: Skip looping if there are no listeners from the API - if (this.emitter.listenerCount(routes.events.EventType.voluntaryExit)) { - for (const voluntaryExit of block.message.body.voluntaryExits) { - this.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit); + // We want to import block asap so call all event handler in the next event loop + callInNextEventLoop(() => { + // NOTE: Skip emitting if there are no listeners from the API + if (this.emitter.listenerCount(routes.events.EventType.block)) { + this.emitter.emit(routes.events.EventType.block, { + block: blockRootHex, + slot: blockSlot, + executionOptimistic: blockSummary != null && isOptimisticBlock(blockSummary), + }); } - } - if (this.emitter.listenerCount(routes.events.EventType.blsToExecutionChange)) { - for (const blsToExecutionChange of (block.message.body as capella.BeaconBlockBody).blsToExecutionChanges ?? []) { - this.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange); + if (this.emitter.listenerCount(routes.events.EventType.voluntaryExit)) { + for (const voluntaryExit of block.message.body.voluntaryExits) { + this.emitter.emit(routes.events.EventType.voluntaryExit, voluntaryExit); + } } - } - if (this.emitter.listenerCount(routes.events.EventType.attestation)) { - for (const attestation of block.message.body.attestations) { - this.emitter.emit(routes.events.EventType.attestation, attestation); + if (this.emitter.listenerCount(routes.events.EventType.blsToExecutionChange)) { + for (const blsToExecutionChange of (block.message as capella.BeaconBlock).body.blsToExecutionChanges ?? []) { + this.emitter.emit(routes.events.EventType.blsToExecutionChange, blsToExecutionChange); + } } - } - if (this.emitter.listenerCount(routes.events.EventType.attesterSlashing)) { - for (const attesterSlashing of block.message.body.attesterSlashings) { - this.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing); + if (this.emitter.listenerCount(routes.events.EventType.attestation)) { + for (const attestation of block.message.body.attestations) { + this.emitter.emit(routes.events.EventType.attestation, attestation); + } } - } - if (this.emitter.listenerCount(routes.events.EventType.proposerSlashing)) { - for (const proposerSlashing of block.message.body.proposerSlashings) { - this.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing); + if (this.emitter.listenerCount(routes.events.EventType.attesterSlashing)) { + for (const attesterSlashing of block.message.body.attesterSlashings) { + this.emitter.emit(routes.events.EventType.attesterSlashing, attesterSlashing); + } } - } + if (this.emitter.listenerCount(routes.events.EventType.proposerSlashing)) { + for (const proposerSlashing of block.message.body.proposerSlashings) { + this.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing); + } + } + if ( + blockInput.type === BlockInputType.availableData && + this.emitter.listenerCount(routes.events.EventType.blobSidecar) + ) { + const {blobs} = blockInput.blockData; + for (const blobSidecar of blobs) { + const {index, kzgCommitment} = blobSidecar; + this.emitter.emit(routes.events.EventType.blobSidecar, { + blockRoot: blockRootHex, + slot: blockSlot, + index, + kzgCommitment: toHexString(kzgCommitment), + versionedHash: toHexString(kzgCommitmentToVersionedHash(kzgCommitment)), + }); + } + } + }); } // Register stat metrics about the block after importing it @@ -452,6 +450,13 @@ export async function importBlock( fullyVerifiedBlock.postState.epochCtx.currentSyncCommitteeIndexed.validatorIndices ); } + // dataPromise will not end up here, but preDeneb could. In future we might also allow syncing + // out of data range blocks and import then in forkchoice although one would not be able to + // attest and propose with such head similar to optimistic sync + if (blockInput.type === BlockInputType.availableData) { + const {blobsSource} = blockInput.blockData; + this.metrics?.importBlock.blobsBySource.inc({blobsSource}); + } const advancedSlot = this.clock.slotWithFutureTolerance(REPROCESS_MIN_TIME_TO_NEXT_SLOT_SEC); diff --git a/packages/beacon-node/src/chain/bls/interface.ts b/packages/beacon-node/src/chain/bls/interface.ts index e9c98ba1920e..f37db9b34aeb 100644 --- a/packages/beacon-node/src/chain/bls/interface.ts +++ b/packages/beacon-node/src/chain/bls/interface.ts @@ -1,4 +1,4 @@ -import {PublicKey} from "@chainsafe/bls/types"; +import {PublicKey} from "@chainsafe/blst"; import {ISignatureSet} from "@lodestar/state-transition"; export type VerifySignatureOpts = { diff --git a/packages/beacon-node/src/chain/bls/maybeBatch.ts b/packages/beacon-node/src/chain/bls/maybeBatch.ts index 619ddf4d72ec..e300b8ff8b76 100644 --- a/packages/beacon-node/src/chain/bls/maybeBatch.ts +++ b/packages/beacon-node/src/chain/bls/maybeBatch.ts @@ -1,5 +1,4 @@ -import {CoordType, PublicKey} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; +import {PublicKey, Signature, verify, verifyMultipleAggregateSignatures} from "@chainsafe/blst"; const MIN_SET_COUNT_TO_BATCH = 2; @@ -16,12 +15,12 @@ export type SignatureSetDeserialized = { export function verifySignatureSetsMaybeBatch(sets: SignatureSetDeserialized[]): boolean { try { if (sets.length >= MIN_SET_COUNT_TO_BATCH) { - return bls.Signature.verifyMultipleSignatures( + return verifyMultipleAggregateSignatures( sets.map((s) => ({ - publicKey: s.publicKey, - message: s.message, + pk: s.publicKey, + msg: s.message, // true = validate signature - signature: bls.Signature.fromBytes(s.signature, CoordType.affine, true), + sig: Signature.fromBytes(s.signature, true), })) ); } @@ -34,8 +33,8 @@ export function verifySignatureSetsMaybeBatch(sets: SignatureSetDeserialized[]): // If too few signature sets verify them without batching return sets.every((set) => { // true = validate signature - const sig = bls.Signature.fromBytes(set.signature, CoordType.affine, true); - return sig.verify(set.publicKey, set.message); + const sig = Signature.fromBytes(set.signature, true); + return verify(set.message, set.publicKey, sig); }); } catch (_) { // A signature could be malformed, in that case fromBytes throws error diff --git a/packages/beacon-node/src/chain/bls/multithread/index.ts b/packages/beacon-node/src/chain/bls/multithread/index.ts index 23e6f1bb460b..3725fa4bcb1c 100644 --- a/packages/beacon-node/src/chain/bls/multithread/index.ts +++ b/packages/beacon-node/src/chain/bls/multithread/index.ts @@ -7,8 +7,7 @@ import {spawn, Worker} from "@chainsafe/threads"; // @ts-ignore // eslint-disable-next-line self = undefined; -import bls from "@chainsafe/bls"; -import {Implementation, PointFormat, PublicKey} from "@chainsafe/bls/types"; +import {PublicKey} from "@chainsafe/blst"; import {Logger} from "@lodestar/utils"; import {ISignatureSet} from "@lodestar/state-transition"; import {QueueError, QueueErrorCode} from "../../../util/queue/index.js"; @@ -116,7 +115,6 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier { private readonly logger: Logger; private readonly metrics: Metrics | null; - private readonly format: PointFormat; private readonly workers: WorkerDescriptor[]; private readonly jobs = new LinkedList(); private bufferedJobs: { @@ -136,14 +134,10 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier { this.metrics = metrics; this.blsVerifyAllMultiThread = options.blsVerifyAllMultiThread ?? false; - // TODO: Allow to customize implementation - const implementation = bls.implementation; - // Use compressed for herumi for now. // THe worker is not able to deserialize from uncompressed // `Error: err _wrapDeserialize` - this.format = implementation === "blst-native" ? PointFormat.uncompressed : PointFormat.compressed; - this.workers = this.createWorkers(implementation, blsPoolSize); + this.workers = this.createWorkers(blsPoolSize); if (metrics) { metrics.blsThreadPool.queueLength.addCollect(() => { @@ -265,11 +259,11 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier { ); } - private createWorkers(implementation: Implementation, poolSize: number): WorkerDescriptor[] { + private createWorkers(poolSize: number): WorkerDescriptor[] { const workers: WorkerDescriptor[] = []; for (let i = 0; i < poolSize; i++) { - const workerData: WorkerData = {implementation, workerId: i}; + const workerData: WorkerData = {workerId: i}; const worker = new Worker(path.join(workerDir, "worker.js"), { workerData, } as ConstructorParameters[1]); @@ -400,7 +394,7 @@ export class BlsMultiThreadWorkerPool implements IBlsVerifier { try { // Note: This can throw, must be handled per-job. // Pubkey and signature aggregation is defered here - workReq = jobItemWorkReq(job, this.format, this.metrics); + workReq = jobItemWorkReq(job, this.metrics); } catch (e) { this.metrics?.blsThreadPool.errorAggregateSignatureSetsCount.inc({type: job.type}); diff --git a/packages/beacon-node/src/chain/bls/multithread/jobItem.ts b/packages/beacon-node/src/chain/bls/multithread/jobItem.ts index 8b5c63df2eeb..035d56e56df2 100644 --- a/packages/beacon-node/src/chain/bls/multithread/jobItem.ts +++ b/packages/beacon-node/src/chain/bls/multithread/jobItem.ts @@ -1,5 +1,4 @@ -import bls from "@chainsafe/bls"; -import {CoordType, PointFormat, PublicKey} from "@chainsafe/bls/types"; +import {PublicKey, aggregateWithRandomness} from "@chainsafe/blst"; import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition"; import {VerifySignatureOpts} from "../interface.js"; import {getAggregatedPubkey} from "../utils.js"; @@ -49,36 +48,37 @@ export function jobItemSigSets(job: JobQueueItem): number { * Prepare BlsWorkReq from JobQueueItem * WARNING: May throw with untrusted user input */ -export function jobItemWorkReq(job: JobQueueItem, format: PointFormat, metrics: Metrics | null): BlsWorkReq { +export function jobItemWorkReq(job: JobQueueItem, metrics: Metrics | null): BlsWorkReq { switch (job.type) { case JobQueueItemType.default: return { opts: job.opts, sets: job.sets.map((set) => ({ // this can throw, handled in the consumer code - publicKey: getAggregatedPubkey(set, metrics).toBytes(format), + publicKey: getAggregatedPubkey(set, metrics).toBytes(), signature: set.signature, message: set.signingRoot, })), }; case JobQueueItemType.sameMessage: { - // validate signature = true, this is slow code on main thread so should only run with network thread mode (useWorker=true) - // For a node subscribing to all subnets, with 1 signature per validator per epoch it takes around 80s - // to deserialize 750_000 signatures per epoch + // This is slow code on main thread (mainly signature deserialization + group check). + // Ideally it can be taken off-thread, but in the mean time, keep track of total time spent here. + // As of July 2024, for a node subscribing to all subnets, with 1 signature per validator per epoch, + // it takes around 2.02 min to perform this operation for a single epoch. // cpu profile on main thread has 250s idle so this only works until we reach 3M validators // However, for normal node with only 2 to 7 subnet subscriptions per epoch this works until 27M validators // and not a problem in the near future - // this is monitored on v1.11.0 https://github.com/ChainSafe/lodestar/pull/5912#issuecomment-1700320307 - const timer = metrics?.blsThreadPool.signatureDeserializationMainThreadDuration.startTimer(); - const signatures = job.sets.map((set) => bls.Signature.fromBytes(set.signature, CoordType.affine, true)); + // this is monitored on v1.21.0 https://github.com/ChainSafe/lodestar/pull/6894/files#r1687359225 + const timer = metrics?.blsThreadPool.aggregateWithRandomnessMainThreadDuration.startTimer(); + const {pk, sig} = aggregateWithRandomness(job.sets.map((set) => ({pk: set.publicKey, sig: set.signature}))); timer?.(); return { opts: job.opts, sets: [ { - publicKey: bls.PublicKey.aggregate(job.sets.map((set) => set.publicKey)).toBytes(format), - signature: bls.Signature.aggregate(signatures).toBytes(format), + publicKey: pk.toBytes(), + signature: sig.toBytes(), message: job.message, }, ], diff --git a/packages/beacon-node/src/chain/bls/multithread/types.ts b/packages/beacon-node/src/chain/bls/multithread/types.ts index 3ebc979b559e..74d400a9aa62 100644 --- a/packages/beacon-node/src/chain/bls/multithread/types.ts +++ b/packages/beacon-node/src/chain/bls/multithread/types.ts @@ -1,7 +1,6 @@ import {VerifySignatureOpts} from "../interface.js"; export type WorkerData = { - implementation: "herumi" | "blst-native"; workerId: number; }; diff --git a/packages/beacon-node/src/chain/bls/multithread/worker.ts b/packages/beacon-node/src/chain/bls/multithread/worker.ts index 0db88dcfccd8..9073920df19c 100644 --- a/packages/beacon-node/src/chain/bls/multithread/worker.ts +++ b/packages/beacon-node/src/chain/bls/multithread/worker.ts @@ -1,8 +1,7 @@ /* eslint-disable @typescript-eslint/strict-boolean-expressions */ import worker from "node:worker_threads"; import {expose} from "@chainsafe/threads/worker"; -import bls from "@chainsafe/bls"; -import {CoordType} from "@chainsafe/bls/types"; +import {PublicKey} from "@chainsafe/blst"; import {verifySignatureSetsMaybeBatch, SignatureSetDeserialized} from "../maybeBatch.js"; import {WorkerData, BlsWorkReq, WorkResult, WorkResultCode, SerializedSet, BlsWorkResult} from "./types.js"; import {chunkifyMaximizeChunkSize} from "./utils.js"; @@ -109,7 +108,7 @@ function verifyManySignatureSets(workReqArr: BlsWorkReq[]): BlsWorkResult { function deserializeSet(set: SerializedSet): SignatureSetDeserialized { return { - publicKey: bls.PublicKey.fromBytes(set.publicKey, CoordType.affine), + publicKey: PublicKey.fromBytes(set.publicKey), message: set.message, signature: set.signature, }; diff --git a/packages/beacon-node/src/chain/bls/singleThread.ts b/packages/beacon-node/src/chain/bls/singleThread.ts index 58ef6b6d9eec..4e2875b1cea9 100644 --- a/packages/beacon-node/src/chain/bls/singleThread.ts +++ b/packages/beacon-node/src/chain/bls/singleThread.ts @@ -1,6 +1,4 @@ -import {PublicKey, Signature} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; -import {CoordType} from "@chainsafe/blst"; +import {PublicKey, Signature, aggregatePublicKeys, aggregateSignatures, verify} from "@chainsafe/blst"; import {ISignatureSet} from "@lodestar/state-transition"; import {Metrics} from "../../metrics/index.js"; import {IBlsVerifier} from "./interface.js"; @@ -40,12 +38,12 @@ export class BlsSingleThreadVerifier implements IBlsVerifier { message: Uint8Array ): Promise { const timer = this.metrics?.blsThreadPool.mainThreadDurationInThreadPool.startTimer(); - const pubkey = bls.PublicKey.aggregate(sets.map((set) => set.publicKey)); + const pubkey = aggregatePublicKeys(sets.map((set) => set.publicKey)); let isAllValid = true; // validate signature = true const signatures = sets.map((set) => { try { - return bls.Signature.fromBytes(set.signature, CoordType.affine, true); + return Signature.fromBytes(set.signature, true); } catch (_) { // at least one set has malformed signature isAllValid = false; @@ -54,8 +52,8 @@ export class BlsSingleThreadVerifier implements IBlsVerifier { }); if (isAllValid) { - const signature = bls.Signature.aggregate(signatures as Signature[]); - isAllValid = signature.verify(pubkey, message); + const signature = aggregateSignatures(signatures as Signature[]); + isAllValid = verify(message, pubkey, signature); } let result: boolean[]; @@ -67,7 +65,7 @@ export class BlsSingleThreadVerifier implements IBlsVerifier { if (sig === null) { return false; } - return sig.verify(set.publicKey, message); + return verify(message, set.publicKey, sig); }); } diff --git a/packages/beacon-node/src/chain/bls/utils.ts b/packages/beacon-node/src/chain/bls/utils.ts index 4a3a027f31ac..63f2bdd80458 100644 --- a/packages/beacon-node/src/chain/bls/utils.ts +++ b/packages/beacon-node/src/chain/bls/utils.ts @@ -1,5 +1,4 @@ -import type {PublicKey} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; +import {PublicKey, aggregatePublicKeys} from "@chainsafe/blst"; import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition"; import {Metrics} from "../../metrics/metrics.js"; @@ -10,7 +9,7 @@ export function getAggregatedPubkey(signatureSet: ISignatureSet, metrics: Metric case SignatureSetType.aggregate: { const timer = metrics?.blsThreadPool.pubkeysAggregationMainThreadDuration.startTimer(); - const pubkeys = bls.PublicKey.aggregate(signatureSet.pubkeys); + const pubkeys = aggregatePublicKeys(signatureSet.pubkeys); timer?.(); return pubkeys; } diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index a6912d952b68..a12ee4a21f64 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -36,7 +36,7 @@ import { import {CheckpointWithHex, ExecutionStatus, IForkChoice, ProtoBlock, UpdateHeadOpt} from "@lodestar/fork-choice"; import {ProcessShutdownCallback} from "@lodestar/validator"; import {Logger, gweiToWei, isErrorAborted, pruneSetToMax, sleep, toHex} from "@lodestar/utils"; -import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {ForkSeq, GENESIS_SLOT, SLOTS_PER_EPOCH} from "@lodestar/params"; import {GENESIS_EPOCH, ZERO_HASH} from "../constants/index.js"; import {IBeaconDb} from "../db/index.js"; @@ -89,6 +89,7 @@ import {BlockAttributes, produceBlockBody, produceCommonBlockBody} from "./produ import {computeNewStateRoot} from "./produceBlock/computeNewStateRoot.js"; import {BlockInput} from "./blocks/types.js"; import {SeenAttestationDatas} from "./seenCache/seenAttestationData.js"; +import {HistoricalStateRegen} from "./historicalState/index.js"; import {BlockRewards, computeBlockRewards} from "./rewards/blockRewards.js"; import {ShufflingCache} from "./shufflingCache.js"; import {BlockStateCacheImpl} from "./stateCache/blockStateCacheImpl.js"; @@ -126,8 +127,9 @@ export class BeaconChain implements IBeaconChain { readonly clock: IClock; readonly emitter: ChainEventEmitter; readonly regen: QueuedStateRegenerator; - readonly lightClientServer: LightClientServer; + readonly lightClientServer?: LightClientServer; readonly reprocessController: ReprocessController; + readonly historicalStateRegen?: HistoricalStateRegen; // Ops pool readonly attestationPool: AttestationPool; @@ -185,6 +187,7 @@ export class BeaconChain implements IBeaconChain { eth1, executionEngine, executionBuilder, + historicalStateRegen, }: { config: BeaconConfig; db: IBeaconDb; @@ -197,6 +200,7 @@ export class BeaconChain implements IBeaconChain { eth1: IEth1ForBlockProduction; executionEngine: IExecutionEngine; executionBuilder?: IExecutionBuilder; + historicalStateRegen?: HistoricalStateRegen; } ) { this.opts = opts; @@ -211,6 +215,7 @@ export class BeaconChain implements IBeaconChain { this.eth1 = eth1; this.executionEngine = executionEngine; this.executionBuilder = executionBuilder; + this.historicalStateRegen = historicalStateRegen; const signal = this.abortController.signal; const emitter = new ChainEventEmitter(); // by default, verify signatures on both main threads and worker threads @@ -305,7 +310,9 @@ export class BeaconChain implements IBeaconChain { signal, }); - const lightClientServer = new LightClientServer(opts, {config, db, metrics, emitter, logger}); + if (!opts.disableLightClientServer) { + this.lightClientServer = new LightClientServer(opts, {config, db, metrics, emitter, logger}); + } this.reprocessController = new ReprocessController(this.metrics); @@ -316,7 +323,6 @@ export class BeaconChain implements IBeaconChain { this.regen = regen; this.bls = bls; this.emitter = emitter; - this.lightClientServer = lightClientServer; this.archiver = new Archiver(db, this, logger, signal, opts); // always run PrepareNextSlotScheduler except for fork_choice spec tests @@ -417,39 +423,63 @@ export class BeaconChain implements IBeaconChain { ): Promise<{state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null> { const finalizedBlock = this.forkChoice.getFinalizedBlock(); - if (slot >= finalizedBlock.slot) { - // request for non-finalized state - - if (opts?.allowRegen) { - // Find closest canonical block to slot, then trigger regen - const block = this.forkChoice.getCanonicalBlockClosestLteSlot(slot) ?? finalizedBlock; - const state = await this.regen.getBlockSlotState( - block.blockRoot, - slot, - {dontTransferCache: true}, - RegenCaller.restApi - ); - return {state, executionOptimistic: isOptimisticBlock(block), finalized: slot === finalizedBlock.slot}; - } else { - // Just check if state is already in the cache. If it's not dialed to the correct slot, - // do not bother in advancing the state. restApiCanTriggerRegen == false means do no work - const block = this.forkChoice.getCanonicalBlockAtSlot(slot); - if (!block) { - return null; - } + if (slot < finalizedBlock.slot) { + // request for finalized state not supported in this API + // fall back to caller to look in db or getHistoricalStateBySlot + return null; + } - const state = this.regen.getStateSync(block.stateRoot); - return state && {state, executionOptimistic: isOptimisticBlock(block), finalized: slot === finalizedBlock.slot}; - } + if (opts?.allowRegen) { + // Find closest canonical block to slot, then trigger regen + const block = this.forkChoice.getCanonicalBlockClosestLteSlot(slot) ?? finalizedBlock; + const state = await this.regen.getBlockSlotState( + block.blockRoot, + slot, + {dontTransferCache: true}, + RegenCaller.restApi + ); + return { + state, + executionOptimistic: isOptimisticBlock(block), + finalized: slot === finalizedBlock.slot && finalizedBlock.slot !== GENESIS_SLOT, + }; } else { - // request for finalized state + // Just check if state is already in the cache. If it's not dialed to the correct slot, + // do not bother in advancing the state. restApiCanTriggerRegen == false means do no work + const block = this.forkChoice.getCanonicalBlockAtSlot(slot); + if (!block) { + return null; + } - // do not attempt regen, just check if state is already in DB - const state = await this.db.stateArchive.get(slot); - return state && {state, executionOptimistic: false, finalized: true}; + const state = this.regen.getStateSync(block.stateRoot); + return ( + state && { + state, + executionOptimistic: isOptimisticBlock(block), + finalized: slot === finalizedBlock.slot && finalizedBlock.slot !== GENESIS_SLOT, + } + ); } } + async getHistoricalStateBySlot( + slot: number + ): Promise<{state: Uint8Array; executionOptimistic: boolean; finalized: boolean} | null> { + const finalizedBlock = this.forkChoice.getFinalizedBlock(); + + if (slot >= finalizedBlock.slot) { + return null; + } + + // request for finalized state using historical state regen + const stateSerialized = await this.historicalStateRegen?.getHistoricalState(slot); + if (!stateSerialized) { + return null; + } + + return {state: stateSerialized, executionOptimistic: false, finalized: true}; + } + async getStateByStateRoot( stateRoot: RootHex, opts?: StateGetOpts @@ -457,10 +487,11 @@ export class BeaconChain implements IBeaconChain { if (opts?.allowRegen) { const state = await this.regen.getState(stateRoot, RegenCaller.restApi); const block = this.forkChoice.getBlock(state.latestBlockHeader.hashTreeRoot()); + const finalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch; return { state, executionOptimistic: block != null && isOptimisticBlock(block), - finalized: state.epochCtx.epoch <= this.forkChoice.getFinalizedCheckpoint().epoch, + finalized: state.epochCtx.epoch <= finalizedEpoch && finalizedEpoch !== GENESIS_EPOCH, }; } @@ -472,10 +503,11 @@ export class BeaconChain implements IBeaconChain { const cachedStateCtx = this.regen.getStateSync(stateRoot); if (cachedStateCtx) { const block = this.forkChoice.getBlock(cachedStateCtx.latestBlockHeader.hashTreeRoot()); + const finalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch; return { state: cachedStateCtx, executionOptimistic: block != null && isOptimisticBlock(block), - finalized: cachedStateCtx.epochCtx.epoch <= this.forkChoice.getFinalizedCheckpoint().epoch, + finalized: cachedStateCtx.epochCtx.epoch <= finalizedEpoch && finalizedEpoch !== GENESIS_EPOCH, }; } @@ -486,14 +518,32 @@ export class BeaconChain implements IBeaconChain { getStateByCheckpoint( checkpoint: CheckpointWithHex ): {state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null { - // TODO: this is not guaranteed to work with new state caches, should work on this before we turn n-historical state on + // finalized or justified checkpoint states maynot be available with PersistentCheckpointStateCache, use getCheckpointStateOrBytes() api to get Uint8Array const cachedStateCtx = this.regen.getCheckpointStateSync(checkpoint); if (cachedStateCtx) { const block = this.forkChoice.getBlock(cachedStateCtx.latestBlockHeader.hashTreeRoot()); + const finalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch; + return { + state: cachedStateCtx, + executionOptimistic: block != null && isOptimisticBlock(block), + finalized: cachedStateCtx.epochCtx.epoch <= finalizedEpoch && finalizedEpoch !== GENESIS_EPOCH, + }; + } + + return null; + } + + async getStateOrBytesByCheckpoint( + checkpoint: CheckpointWithHex + ): Promise<{state: CachedBeaconStateAllForks | Uint8Array; executionOptimistic: boolean; finalized: boolean} | null> { + const cachedStateCtx = await this.regen.getCheckpointStateOrBytes(checkpoint); + if (cachedStateCtx) { + const block = this.forkChoice.getBlock(checkpoint.root); + const finalizedEpoch = this.forkChoice.getFinalizedCheckpoint().epoch; return { state: cachedStateCtx, executionOptimistic: block != null && isOptimisticBlock(block), - finalized: cachedStateCtx.epochCtx.epoch <= this.forkChoice.getFinalizedCheckpoint().epoch, + finalized: checkpoint.epoch <= finalizedEpoch && finalizedEpoch !== GENESIS_EPOCH, }; } diff --git a/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts new file mode 100644 index 000000000000..ada4f3c284d7 --- /dev/null +++ b/packages/beacon-node/src/chain/historicalState/getHistoricalState.ts @@ -0,0 +1,111 @@ +import { + BeaconStateAllForks, + CachedBeaconStateAllForks, + DataAvailableStatus, + ExecutionPayloadStatus, + PubkeyIndexMap, + createCachedBeaconState, + stateTransition, +} from "@lodestar/state-transition"; +import {BeaconConfig} from "@lodestar/config"; +import {IBeaconDb} from "../../db/index.js"; +import {HistoricalStateRegenMetrics, RegenErrorType} from "./types.js"; + +/** + * Populate a PubkeyIndexMap with any new entries based on a BeaconState + */ +export function syncPubkeyCache(state: BeaconStateAllForks, pubkey2index: PubkeyIndexMap): void { + // Get the validators sub tree once for all the loop + const validators = state.validators; + + const newCount = state.validators.length; + for (let i = pubkey2index.size; i < newCount; i++) { + const pubkey = validators.getReadonly(i).pubkey; + pubkey2index.set(pubkey, i); + } +} + +/** + * Get the nearest BeaconState at or before a slot + */ +export async function getNearestState( + slot: number, + config: BeaconConfig, + db: IBeaconDb, + pubkey2index: PubkeyIndexMap +): Promise { + const states = await db.stateArchive.values({limit: 1, lte: slot, reverse: true}); + if (!states.length) { + throw new Error("No near state found in the database"); + } + + const state = states[0]; + syncPubkeyCache(state, pubkey2index); + + return createCachedBeaconState( + state, + { + config, + pubkey2index, + index2pubkey: [], + }, + { + skipSyncPubkeys: true, + } + ); +} + +/** + * Get and regenerate a historical state + */ +export async function getHistoricalState( + slot: number, + config: BeaconConfig, + db: IBeaconDb, + pubkey2index: PubkeyIndexMap, + metrics?: HistoricalStateRegenMetrics +): Promise { + const regenTimer = metrics?.regenTime.startTimer(); + + const loadStateTimer = metrics?.loadStateTime.startTimer(); + let state = await getNearestState(slot, config, db, pubkey2index).catch((e) => { + metrics?.regenErrorCount.inc({reason: RegenErrorType.loadState}); + throw e; + }); + loadStateTimer?.(); + + const transitionTimer = metrics?.stateTransitionTime.startTimer(); + let blockCount = 0; + for await (const block of db.blockArchive.valuesStream({gt: state.slot, lte: slot})) { + try { + state = stateTransition( + state, + block, + { + verifyProposer: false, + verifySignatures: false, + verifyStateRoot: false, + executionPayloadStatus: ExecutionPayloadStatus.valid, + dataAvailableStatus: DataAvailableStatus.available, + }, + metrics + ); + } catch (e) { + metrics?.regenErrorCount.inc({reason: RegenErrorType.blockProcessing}); + throw e; + } + blockCount++; + if (Buffer.compare(state.hashTreeRoot(), block.message.stateRoot) !== 0) { + metrics?.regenErrorCount.inc({reason: RegenErrorType.invalidStateRoot}); + } + } + metrics?.stateTransitionBlocks.observe(blockCount); + transitionTimer?.(); + + const serializeTimer = metrics?.stateSerializationTime.startTimer(); + const stateBytes = state.serialize(); + serializeTimer?.(); + + regenTimer?.(); + return stateBytes; +} diff --git a/packages/beacon-node/src/chain/historicalState/index.ts b/packages/beacon-node/src/chain/historicalState/index.ts new file mode 100644 index 000000000000..8688bcf9f372 --- /dev/null +++ b/packages/beacon-node/src/chain/historicalState/index.ts @@ -0,0 +1,67 @@ +import path from "node:path"; +import {ModuleThread, Thread, spawn, Worker} from "@chainsafe/threads"; +import {chainConfigToJson} from "@lodestar/config"; +import {LoggerNode} from "@lodestar/logger/node"; +import { + HistoricalStateRegenInitModules, + HistoricalStateRegenModules, + HistoricalStateWorkerApi, + HistoricalStateWorkerData, +} from "./types.js"; + +// Worker constructor consider the path relative to the current working directory +const WORKER_DIR = process.env.NODE_ENV === "test" ? "../../../lib/chain/historicalState" : "./"; + +/** + * HistoricalStateRegen limits the damage from recreating historical states + * by running regen in a separate worker thread. + */ +export class HistoricalStateRegen implements HistoricalStateWorkerApi { + private readonly api: ModuleThread; + private readonly logger: LoggerNode; + + constructor(modules: HistoricalStateRegenModules) { + this.api = modules.api; + this.logger = modules.logger; + modules.signal?.addEventListener("abort", () => this.close(), {once: true}); + } + static async init(modules: HistoricalStateRegenInitModules): Promise { + const workerData: HistoricalStateWorkerData = { + chainConfigJson: chainConfigToJson(modules.config), + genesisValidatorsRoot: modules.config.genesisValidatorsRoot, + genesisTime: modules.opts.genesisTime, + maxConcurrency: 1, + maxLength: 50, + dbLocation: modules.opts.dbLocation, + metricsEnabled: Boolean(modules.metrics), + loggerOpts: modules.logger.toOpts(), + }; + + const worker = new Worker(path.join(WORKER_DIR, "worker.js"), { + workerData, + } as ConstructorParameters[1]); + + const api = await spawn(worker, { + // A Lodestar Node may do very expensive task at start blocking the event loop and causing + // the initialization to timeout. The number below is big enough to almost disable the timeout + timeout: 5 * 60 * 1000, + }); + + return new HistoricalStateRegen({...modules, api}); + } + + async scrapeMetrics(): Promise { + return this.api.scrapeMetrics(); + } + + async close(): Promise { + await this.api.close(); + this.logger.debug("Terminating historical state worker"); + await Thread.terminate(this.api); + this.logger.debug("Terminated historical state worker"); + } + + async getHistoricalState(slot: number): Promise { + return this.api.getHistoricalState(slot); + } +} diff --git a/packages/beacon-node/src/chain/historicalState/types.ts b/packages/beacon-node/src/chain/historicalState/types.ts new file mode 100644 index 000000000000..5bc813141a5d --- /dev/null +++ b/packages/beacon-node/src/chain/historicalState/types.ts @@ -0,0 +1,54 @@ +import {ModuleThread} from "@chainsafe/threads"; +import {BeaconConfig} from "@lodestar/config"; +import {LoggerNode, LoggerNodeOpts} from "@lodestar/logger/node"; +import {BeaconStateTransitionMetrics} from "@lodestar/state-transition"; +import {Gauge, Histogram} from "@lodestar/utils"; +import {Metrics} from "../../metrics/index.js"; + +export type HistoricalStateRegenInitModules = { + opts: { + genesisTime: number; + dbLocation: string; + }; + config: BeaconConfig; + logger: LoggerNode; + metrics: Metrics | null; + signal?: AbortSignal; +}; +export type HistoricalStateRegenModules = HistoricalStateRegenInitModules & { + api: ModuleThread; +}; + +export type HistoricalStateWorkerData = { + chainConfigJson: Record; + genesisValidatorsRoot: Uint8Array; + genesisTime: number; + maxConcurrency: number; + maxLength: number; + dbLocation: string; + metricsEnabled: boolean; + loggerOpts: LoggerNodeOpts; +}; + +export type HistoricalStateWorkerApi = { + close(): Promise; + scrapeMetrics(): Promise; + getHistoricalState(slot: number): Promise; +}; + +export enum RegenErrorType { + loadState = "load_state", + invalidStateRoot = "invalid_state_root", + blockProcessing = "block_processing", +} + +export type HistoricalStateRegenMetrics = BeaconStateTransitionMetrics & { + regenTime: Histogram; + loadStateTime: Histogram; + stateTransitionTime: Histogram; + stateTransitionBlocks: Histogram; + stateSerializationTime: Histogram; + regenRequestCount: Gauge; + regenSuccessCount: Gauge; + regenErrorCount: Gauge<{reason: RegenErrorType}>; +}; diff --git a/packages/beacon-node/src/chain/historicalState/worker.ts b/packages/beacon-node/src/chain/historicalState/worker.ts new file mode 100644 index 000000000000..9a9f9cc9cd0e --- /dev/null +++ b/packages/beacon-node/src/chain/historicalState/worker.ts @@ -0,0 +1,231 @@ +import worker from "node:worker_threads"; +import {Transfer, expose} from "@chainsafe/threads/worker"; +import {createBeaconConfig, chainConfigFromJson} from "@lodestar/config"; +import {getNodeLogger} from "@lodestar/logger/node"; +import { + EpochTransitionStep, + PubkeyIndexMap, + StateCloneSource, + StateHashTreeRootSource, +} from "@lodestar/state-transition"; +import {LevelDbController} from "@lodestar/db"; +import {RegistryMetricCreator, collectNodeJSMetrics} from "../../metrics/index.js"; +import {JobFnQueue} from "../../util/queue/fnQueue.js"; +import {QueueMetrics} from "../../util/queue/options.js"; +import {BeaconDb} from "../../db/index.js"; +import { + HistoricalStateRegenMetrics, + HistoricalStateWorkerApi, + HistoricalStateWorkerData, + RegenErrorType, +} from "./types.js"; +import {getHistoricalState} from "./getHistoricalState.js"; + +// most of this setup copied from networkCoreWorker.ts + +const workerData = worker.workerData as HistoricalStateWorkerData; + +// TODO: Pass options from main thread for logging +// TODO: Logging won't be visible in file loggers +const logger = getNodeLogger(workerData.loggerOpts); + +logger.info("Historical state worker started"); + +const config = createBeaconConfig(chainConfigFromJson(workerData.chainConfigJson), workerData.genesisValidatorsRoot); + +const db = new BeaconDb(config, await LevelDbController.create({name: workerData.dbLocation}, {logger})); + +const abortController = new AbortController(); + +// Set up metrics, nodejs, state transition, queue +const metricsRegister = workerData.metricsEnabled ? new RegistryMetricCreator() : null; +let historicalStateRegenMetrics: HistoricalStateRegenMetrics | undefined; +let queueMetrics: QueueMetrics | undefined; +if (metricsRegister) { + const closeMetrics = collectNodeJSMetrics(metricsRegister, "lodestar_historical_state_worker_"); + abortController.signal.addEventListener("abort", closeMetrics, {once: true}); + + historicalStateRegenMetrics = { + // state transition metrics + epochTransitionTime: metricsRegister.histogram({ + name: "lodestar_historical_state_stfn_epoch_transition_seconds", + help: "Time to process a single epoch transition in seconds", + // Epoch transitions are 100ms on very fast clients, and average 800ms on heavy networks + buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 0.75, 1, 1.25, 1.5, 3, 10], + }), + epochTransitionCommitTime: metricsRegister.histogram({ + name: "lodestar_historical_state_stfn_epoch_transition_commit_seconds", + help: "Time to call commit after process a single epoch transition in seconds", + buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 0.75, 1], + }), + epochTransitionStepTime: metricsRegister.histogram<{step: EpochTransitionStep}>({ + name: "lodestar_historical_state_stfn_epoch_transition_step_seconds", + help: "Time to call each step of epoch transition in seconds", + labelNames: ["step"], + buckets: [0.01, 0.05, 0.1, 0.2, 0.5, 0.75, 1], + }), + processBlockTime: metricsRegister.histogram({ + name: "lodestar_historical_state_stfn_process_block_seconds", + help: "Time to process a single block in seconds", + // TODO: Add metrics for each step + // Block processing can take 5-40ms, 100ms max + buckets: [0.005, 0.01, 0.02, 0.05, 0.1, 1], + }), + processBlockCommitTime: metricsRegister.histogram({ + name: "lodestar_historical_state_stfn_process_block_commit_seconds", + help: "Time to call commit after process a single block in seconds", + buckets: [0.005, 0.01, 0.02, 0.05, 0.1, 1], + }), + stateHashTreeRootTime: metricsRegister.histogram<{source: StateHashTreeRootSource}>({ + name: "lodestar_historical_state_stfn_hash_tree_root_seconds", + help: "Time to compute the hash tree root of a post state in seconds", + buckets: [0.05, 0.1, 0.2, 0.5, 1, 1.5], + labelNames: ["source"], + }), + preStateBalancesNodesPopulatedMiss: metricsRegister.gauge<{source: StateCloneSource}>({ + name: "lodestar_historical_state_stfn_balances_nodes_populated_miss_total", + help: "Total count state.balances nodesPopulated is false on stfn", + labelNames: ["source"], + }), + preStateBalancesNodesPopulatedHit: metricsRegister.gauge<{source: StateCloneSource}>({ + name: "lodestar_historical_state_stfn_balances_nodes_populated_hit_total", + help: "Total count state.balances nodesPopulated is true on stfn", + labelNames: ["source"], + }), + preStateValidatorsNodesPopulatedMiss: metricsRegister.gauge<{source: StateCloneSource}>({ + name: "lodestar_historical_state_stfn_validators_nodes_populated_miss_total", + help: "Total count state.validators nodesPopulated is false on stfn", + labelNames: ["source"], + }), + preStateValidatorsNodesPopulatedHit: metricsRegister.gauge<{source: StateCloneSource}>({ + name: "lodestar_historical_state_stfn_validators_nodes_populated_hit_total", + help: "Total count state.validators nodesPopulated is true on stfn", + labelNames: ["source"], + }), + preStateClonedCount: metricsRegister.histogram({ + name: "lodestar_historical_state_stfn_state_cloned_count", + help: "Histogram of cloned count per state every time state.clone() is called", + buckets: [1, 2, 5, 10, 50, 250], + }), + postStateBalancesNodesPopulatedHit: metricsRegister.gauge({ + name: "lodestar_historical_state_stfn_post_state_balances_nodes_populated_hit_total", + help: "Total count state.validators nodesPopulated is true on stfn for post state", + }), + postStateBalancesNodesPopulatedMiss: metricsRegister.gauge({ + name: "lodestar_historical_state_stfn_post_state_balances_nodes_populated_miss_total", + help: "Total count state.validators nodesPopulated is false on stfn for post state", + }), + postStateValidatorsNodesPopulatedHit: metricsRegister.gauge({ + name: "lodestar_historical_state_stfn_post_state_validators_nodes_populated_hit_total", + help: "Total count state.validators nodesPopulated is true on stfn for post state", + }), + postStateValidatorsNodesPopulatedMiss: metricsRegister.gauge({ + name: "lodestar_historical_state_stfn_post_state_validators_nodes_populated_miss_total", + help: "Total count state.validators nodesPopulated is false on stfn for post state", + }), + registerValidatorStatuses: () => {}, + + // historical state regen metrics + regenTime: metricsRegister.histogram({ + name: "lodestar_historical_state_regen_time_seconds", + help: "Time to regenerate a historical state in seconds", + // Historical state regen can take up to 3h as of Aug 2024 + // 5m, 10m, 30m, 1h, 3h + buckets: [5 * 60, 10 * 60, 30 * 60, 60 * 60, 180 * 60], + }), + loadStateTime: metricsRegister.histogram({ + name: "lodestar_historical_state_load_nearest_state_time_seconds", + help: "Time to load a nearest historical state from the database in seconds", + // 30s, 1m, 2m, 4m + buckets: [30, 60, 120, 240], + }), + stateTransitionTime: metricsRegister.histogram({ + name: "lodestar_historical_state_state_transition_time_seconds", + help: "Time to run state transition to regen historical state in seconds", + // 5m, 10m, 30m, 1h, 3h + buckets: [5 * 60, 10 * 60, 30 * 60, 60 * 60, 180 * 60], + }), + stateTransitionBlocks: metricsRegister.histogram({ + name: "lodestar_historical_state_state_transition_blocks", + help: "Count of blocks processed during state transition to regen historical state", + // given archiveStateEpochFrequency=1024, it could process up to 32768 blocks + buckets: [10, 100, 1000, 10000, 30000], + }), + stateSerializationTime: metricsRegister.histogram({ + name: "lodestar_historical_state_serialization_time_seconds", + help: "Time to serialize a historical state in seconds", + buckets: [0.25, 0.5, 1, 2], + }), + regenRequestCount: metricsRegister.gauge({ + name: "lodestar_historical_state_request_count", + help: "Count of total historical state requests", + }), + regenSuccessCount: metricsRegister.gauge({ + name: "lodestar_historical_state_success_count", + help: "Count of successful historical state regen", + }), + regenErrorCount: metricsRegister.gauge<{reason: RegenErrorType}>({ + name: "lodestar_historical_state_error_count", + help: "Count of failed historical state regen", + labelNames: ["reason"], + }), + }; + + queueMetrics = { + length: metricsRegister.gauge({ + name: "lodestar_historical_state_queue_length", + help: "Count of total regen queue length", + }), + droppedJobs: metricsRegister.gauge({ + name: "lodestar_historical_state_queue_dropped_jobs_total", + help: "Count of total regen queue dropped jobs", + }), + jobTime: metricsRegister.histogram({ + name: "lodestar_historical_state_queue_job_time_seconds", + help: "Time to process regen queue job in seconds", + buckets: [0.01, 0.1, 1, 10, 100], + }), + jobWaitTime: metricsRegister.histogram({ + name: "lodestar_historical_state_queue_job_wait_time_seconds", + help: "Time from job added to the regen queue to starting in seconds", + buckets: [0.01, 0.1, 1, 10, 100], + }), + concurrency: metricsRegister.gauge({ + name: "lodestar_historical_state_queue_concurrency", + help: "Current concurrency of regen queue", + }), + }; +} + +const queue = new JobFnQueue( + { + maxConcurrency: workerData.maxConcurrency, + maxLength: workerData.maxLength, + signal: abortController.signal, + }, + queueMetrics +); + +const pubkey2index = new PubkeyIndexMap(); + +const api: HistoricalStateWorkerApi = { + async close() { + abortController.abort(); + }, + async scrapeMetrics() { + return metricsRegister?.metrics() ?? ""; + }, + async getHistoricalState(slot) { + historicalStateRegenMetrics?.regenRequestCount.inc(); + + const stateBytes = await queue.push(() => + getHistoricalState(slot, config, db, pubkey2index, historicalStateRegenMetrics) + ); + const result = Transfer(stateBytes, [stateBytes.buffer]) as unknown as Uint8Array; + + historicalStateRegenMetrics?.regenSuccessCount.inc(); + return result; + }, +}; + +expose(api); diff --git a/packages/beacon-node/src/chain/interface.ts b/packages/beacon-node/src/chain/interface.ts index e412d8e8aafa..ca13dc604ea0 100644 --- a/packages/beacon-node/src/chain/interface.ts +++ b/packages/beacon-node/src/chain/interface.ts @@ -95,7 +95,7 @@ export interface IBeaconChain { readonly clock: IClock; readonly emitter: ChainEventEmitter; readonly regen: IStateRegenerator; - readonly lightClientServer: LightClientServer; + readonly lightClientServer?: LightClientServer; readonly reprocessController: ReprocessController; readonly pubkey2index: PubkeyIndexMap; readonly index2pubkey: Index2PubkeyCache; @@ -142,6 +142,10 @@ export interface IBeaconChain { getHeadStateAtCurrentEpoch(regenCaller: RegenCaller): Promise; getHeadStateAtEpoch(epoch: Epoch, regenCaller: RegenCaller): Promise; + getHistoricalStateBySlot( + slot: Slot + ): Promise<{state: Uint8Array; executionOptimistic: boolean; finalized: boolean} | null>; + /** Returns a local state canonical at `slot` */ getStateBySlot( slot: Slot, @@ -156,6 +160,10 @@ export interface IBeaconChain { getStateByCheckpoint( checkpoint: CheckpointWithHex ): {state: BeaconStateAllForks; executionOptimistic: boolean; finalized: boolean} | null; + /** Return state bytes by checkpoint */ + getStateOrBytesByCheckpoint( + checkpoint: CheckpointWithHex + ): Promise<{state: CachedBeaconStateAllForks | Uint8Array; executionOptimistic: boolean; finalized: boolean} | null>; /** * Since we can have multiple parallel chains, diff --git a/packages/beacon-node/src/chain/lightClient/index.ts b/packages/beacon-node/src/chain/lightClient/index.ts index 6d713109f945..0230ca48c8be 100644 --- a/packages/beacon-node/src/chain/lightClient/index.ts +++ b/packages/beacon-node/src/chain/lightClient/index.ts @@ -60,6 +60,7 @@ import { export type LightClientServerOpts = { disableLightClientServerOnImportBlockHead?: boolean; + disableLightClientServer?: boolean; }; type DependentRootHex = RootHex; diff --git a/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts b/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts index c94e5d81e823..556e1f397d60 100644 --- a/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts +++ b/packages/beacon-node/src/chain/opPools/aggregatedAttestationPool.ts @@ -1,5 +1,5 @@ -import bls from "@chainsafe/bls"; import {toHexString} from "@chainsafe/ssz"; +import {aggregateSignatures} from "@chainsafe/blst"; import {ForkName, ForkSeq, MAX_ATTESTATIONS, MIN_ATTESTATION_INCLUSION_DELAY, SLOTS_PER_EPOCH} from "@lodestar/params"; import {phase0, Epoch, Slot, ssz, ValidatorIndex, RootHex} from "@lodestar/types"; import { @@ -383,7 +383,7 @@ export function aggregateInto(attestation1: AttestationWithIndex, attestation2: const signature1 = signatureFromBytesNoCheck(attestation1.attestation.signature); const signature2 = signatureFromBytesNoCheck(attestation2.attestation.signature); - attestation1.attestation.signature = bls.Signature.aggregate([signature1, signature2]).toBytes(); + attestation1.attestation.signature = aggregateSignatures([signature1, signature2]).toBytes(); } /** diff --git a/packages/beacon-node/src/chain/opPools/attestationPool.ts b/packages/beacon-node/src/chain/opPools/attestationPool.ts index 804d8798cbc2..2b511598f9a4 100644 --- a/packages/beacon-node/src/chain/opPools/attestationPool.ts +++ b/packages/beacon-node/src/chain/opPools/attestationPool.ts @@ -1,6 +1,5 @@ -import {PointFormat, Signature} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; import {BitArray} from "@chainsafe/ssz"; +import {Signature, aggregateSignatures} from "@chainsafe/blst"; import {phase0, Slot, RootHex} from "@lodestar/types"; import {MapDef} from "@lodestar/utils"; import {IClock} from "../../util/clock.js"; @@ -191,10 +190,7 @@ function aggregateAttestationInto(aggregate: AggregateFast, attestation: phase0. } aggregate.aggregationBits.set(bitIndex, true); - aggregate.signature = bls.Signature.aggregate([ - aggregate.signature, - signatureFromBytesNoCheck(attestation.signature), - ]); + aggregate.signature = aggregateSignatures([aggregate.signature, signatureFromBytesNoCheck(attestation.signature)]); return InsertOutcome.Aggregated; } @@ -217,6 +213,6 @@ function fastToAttestation(aggFast: AggregateFast): phase0.Attestation { return { data: aggFast.data, aggregationBits: aggFast.aggregationBits, - signature: aggFast.signature.toBytes(PointFormat.compressed), + signature: aggFast.signature.toBytes(), }; } diff --git a/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts b/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts index 03552992a72a..90a310841f01 100644 --- a/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts +++ b/packages/beacon-node/src/chain/opPools/syncCommitteeMessagePool.ts @@ -1,6 +1,5 @@ -import {PointFormat, Signature} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; import {BitArray, toHexString} from "@chainsafe/ssz"; +import {Signature, aggregateSignatures} from "@chainsafe/blst"; import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; import {altair, Root, Slot, SubcommitteeIndex} from "@lodestar/types"; import {MapDef} from "@lodestar/utils"; @@ -108,7 +107,7 @@ export class SyncCommitteeMessagePool { return { ...contribution, aggregationBits: contribution.aggregationBits, - signature: contribution.signature.toBytes(PointFormat.compressed), + signature: contribution.signature.toBytes(), }; } @@ -136,7 +135,7 @@ function aggregateSignatureInto( } contribution.aggregationBits.set(indexInSubcommittee, true); - contribution.signature = bls.Signature.aggregate([ + contribution.signature = aggregateSignatures([ contribution.signature, signatureFromBytesNoCheck(signature.signature), ]); diff --git a/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts b/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts index 51b433fd6e50..7834ae534501 100644 --- a/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts +++ b/packages/beacon-node/src/chain/opPools/syncContributionAndProofPool.ts @@ -1,6 +1,5 @@ -import type {Signature} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; import {BitArray, toHexString} from "@chainsafe/ssz"; +import {Signature, aggregateSignatures} from "@chainsafe/blst"; import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_SIZE} from "@lodestar/params"; import {altair, Slot, Root, ssz} from "@lodestar/types"; import {G2_POINT_AT_INFINITY} from "@lodestar/state-transition"; @@ -182,6 +181,6 @@ export function aggregate(bestContributionBySubnet: Map, slot: Slot, slotsRetained: * No need to verify Signature is valid, already run sig-verify = false */ export function signatureFromBytesNoCheck(signature: Uint8Array): Signature { - return bls.Signature.fromBytes(signature, CoordType.affine, false); + return Signature.fromBytes(signature); } /** diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index 341625c9ff1d..031d19860789 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -44,7 +44,7 @@ export interface IStateRegenerator extends IStateRegeneratorInternal { pruneOnFinalized(finalizedEpoch: Epoch): void; processState(blockRootHex: RootHex, postState: CachedBeaconStateAllForks): void; addCheckpointState(cp: phase0.Checkpoint, item: CachedBeaconStateAllForks): void; - updateHeadState(newHeadStateRoot: RootHex, maybeHeadState: CachedBeaconStateAllForks): void; + updateHeadState(newHead: ProtoBlock, maybeHeadState: CachedBeaconStateAllForks): void; updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null; } diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index 358a37e6e638..ad673b334bd1 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -165,28 +165,40 @@ export class QueuedStateRegenerator implements IStateRegenerator { this.checkpointStateCache.add(cp, item); } - updateHeadState(newHeadStateRoot: RootHex, maybeHeadState: CachedBeaconStateAllForks): void { - // the resulting state will be added to block state cache so we transfer the cache in this flow - const cloneOpts = {dontTransferCache: true}; + updateHeadState(newHead: ProtoBlock, maybeHeadState: CachedBeaconStateAllForks): void { + const {stateRoot: newHeadStateRoot, blockRoot: newHeadBlockRoot, slot: newHeadSlot} = newHead; + const maybeHeadStateRoot = toHexString(maybeHeadState.hashTreeRoot()); + const logCtx = { + newHeadSlot, + newHeadBlockRoot, + newHeadStateRoot, + maybeHeadSlot: maybeHeadState.slot, + maybeHeadStateRoot, + }; const headState = - newHeadStateRoot === toHexString(maybeHeadState.hashTreeRoot()) + newHeadStateRoot === maybeHeadStateRoot ? maybeHeadState - : this.blockStateCache.get(newHeadStateRoot, cloneOpts); + : // maybeHeadState was already in block state cache so we don't transfer the cache + this.blockStateCache.get(newHeadStateRoot, {dontTransferCache: true}); if (headState) { this.blockStateCache.setHeadState(headState); } else { // Trigger regen on head change if necessary - this.logger.warn("Head state not available, triggering regen", {stateRoot: newHeadStateRoot}); - // it's important to reload state to regen head state here - const allowDiskReload = true; - // head has changed, so the existing cached head state is no longer useful. Set strong reference to null to free - // up memory for regen step below. During regen, node won't be functional but eventually head will be available - // for legacy StateContextCache only + this.logger.warn("Head state not available, triggering regen", logCtx); + // for the old BlockStateCacheImpl only + // - head has changed, so the existing cached head state is no longer useful. Set strong reference to null to free + // up memory for regen step below. During regen, node won't be functional but eventually head will be available + // for the new FIFOBlockStateCache, this has no affect this.blockStateCache.setHeadState(null); + + // for the new FIFOBlockStateCache, it's important to reload state to regen head state here if needed + const allowDiskReload = true; + // transfer cache here because we want to regen state asap + const cloneOpts = {dontTransferCache: false}; this.regen.getState(newHeadStateRoot, RegenCaller.processBlock, cloneOpts, allowDiskReload).then( (headStateRegen) => this.blockStateCache.setHeadState(headStateRegen), - (e) => this.logger.error("Error on head state regen", {}, e) + (e) => this.logger.error("Error on head state regen", logCtx, e) ); } } diff --git a/packages/beacon-node/src/chain/rewards/attestationsRewards.ts b/packages/beacon-node/src/chain/rewards/attestationsRewards.ts index 3b4583826349..e909e4b1b57e 100644 --- a/packages/beacon-node/src/chain/rewards/attestationsRewards.ts +++ b/packages/beacon-node/src/chain/rewards/attestationsRewards.ts @@ -140,7 +140,7 @@ function computeTotalAttestationsRewardsAltair( validatorIds: (ValidatorIndex | string)[] = [] ): TotalAttestationsReward[] { const rewards = []; - const {statuses} = transitionCache; + const {flags} = transitionCache; const {epochCtx, config} = state; const validatorIndices = validatorIds .map((id) => (typeof id === "number" ? id : epochCtx.pubkey2index.get(id))) @@ -148,13 +148,13 @@ function computeTotalAttestationsRewardsAltair( const inactivityPenaltyDenominator = config.INACTIVITY_SCORE_BIAS * INACTIVITY_PENALTY_QUOTIENT_ALTAIR; - for (let i = 0; i < statuses.length; i++) { + for (let i = 0; i < flags.length; i++) { if (validatorIndices.length && !validatorIndices.includes(i)) { continue; } - const status = statuses[i]; - if (!hasMarkers(status.flags, FLAG_ELIGIBLE_ATTESTER)) { + const flag = flags[i]; + if (!hasMarkers(flag, FLAG_ELIGIBLE_ATTESTER)) { continue; } @@ -162,13 +162,13 @@ function computeTotalAttestationsRewardsAltair( const currentRewards = {...defaultAttestationsReward, validatorIndex: i}; - if (hasMarkers(status.flags, FLAG_PREV_SOURCE_ATTESTER_UNSLASHED)) { + if (hasMarkers(flag, FLAG_PREV_SOURCE_ATTESTER_UNSLASHED)) { currentRewards.source = idealRewards[effectiveBalanceIncrement].source; } else { currentRewards.source = penalties[effectiveBalanceIncrement].source * -1; // Negative reward to indicate penalty } - if (hasMarkers(status.flags, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) { + if (hasMarkers(flag, FLAG_PREV_TARGET_ATTESTER_UNSLASHED)) { currentRewards.target = idealRewards[effectiveBalanceIncrement].target; } else { currentRewards.target = penalties[effectiveBalanceIncrement].target * -1; @@ -179,7 +179,7 @@ function computeTotalAttestationsRewardsAltair( currentRewards.inactivity = Math.floor(inactivityPenaltyNumerator / inactivityPenaltyDenominator) * -1; } - if (hasMarkers(status.flags, FLAG_PREV_HEAD_ATTESTER_UNSLASHED)) { + if (hasMarkers(flag, FLAG_PREV_HEAD_ATTESTER_UNSLASHED)) { currentRewards.head = idealRewards[effectiveBalanceIncrement].head; } diff --git a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts index 23b91dba5fd9..f4865c73f8e4 100644 --- a/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientFinalityUpdate.ts @@ -3,6 +3,7 @@ import {LightClientFinalityUpdate} from "@lodestar/types"; import {IBeaconChain} from "../interface.js"; import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js"; import {GossipAction} from "../errors/index.js"; +import {assertLightClientServer} from "../../node/utils/lightclient.js"; import {updateReceivedTooEarly} from "./lightClientOptimisticUpdate.js"; // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#light_client_finality_update @@ -11,6 +12,8 @@ export function validateLightClientFinalityUpdate( chain: IBeaconChain, gossipedFinalityUpdate: LightClientFinalityUpdate ): void { + assertLightClientServer(chain.lightClientServer); + // [IGNORE] No other finality_update with a lower or equal finalized_header.slot was already forwarded on the network const gossipedFinalitySlot = gossipedFinalityUpdate.finalizedHeader.beacon.slot; const localFinalityUpdate = chain.lightClientServer.getFinalityUpdate(); diff --git a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts index 54b69f56808c..182321984af5 100644 --- a/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts +++ b/packages/beacon-node/src/chain/validation/lightClientOptimisticUpdate.ts @@ -5,6 +5,7 @@ import {IBeaconChain} from "../interface.js"; import {LightClientError, LightClientErrorCode} from "../errors/lightClientError.js"; import {GossipAction} from "../errors/index.js"; import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js"; +import {assertLightClientServer} from "../../node/utils/lightclient.js"; // https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/p2p-interface.md#light_client_optimistic_update export function validateLightClientOptimisticUpdate( @@ -12,6 +13,8 @@ export function validateLightClientOptimisticUpdate( chain: IBeaconChain, gossipedOptimisticUpdate: LightClientOptimisticUpdate ): void { + assertLightClientServer(chain.lightClientServer); + // [IGNORE] No other optimistic_update with a lower or equal attested_header.slot was already forwarded on the network const gossipedAttestedSlot = gossipedOptimisticUpdate.attestedHeader.beacon.slot; const localOptimisticUpdate = chain.lightClientServer.getOptimisticUpdate(); diff --git a/packages/beacon-node/src/chain/validation/signatureSets/aggregateAndProof.ts b/packages/beacon-node/src/chain/validation/signatureSets/aggregateAndProof.ts index 2bc2e62c861f..59787341cfb9 100644 --- a/packages/beacon-node/src/chain/validation/signatureSets/aggregateAndProof.ts +++ b/packages/beacon-node/src/chain/validation/signatureSets/aggregateAndProof.ts @@ -1,4 +1,4 @@ -import type {PublicKey} from "@chainsafe/bls/types"; +import {PublicKey} from "@chainsafe/blst"; import {DOMAIN_AGGREGATE_AND_PROOF} from "@lodestar/params"; import {ssz} from "@lodestar/types"; import {Epoch, phase0} from "@lodestar/types"; diff --git a/packages/beacon-node/src/chain/validation/signatureSets/selectionProof.ts b/packages/beacon-node/src/chain/validation/signatureSets/selectionProof.ts index 09e0a5ef12be..5e129a88aa20 100644 --- a/packages/beacon-node/src/chain/validation/signatureSets/selectionProof.ts +++ b/packages/beacon-node/src/chain/validation/signatureSets/selectionProof.ts @@ -1,4 +1,4 @@ -import type {PublicKey} from "@chainsafe/bls/types"; +import {PublicKey} from "@chainsafe/blst"; import {DOMAIN_SELECTION_PROOF} from "@lodestar/params"; import {phase0, Slot, ssz} from "@lodestar/types"; import {computeSigningRoot, createSingleSignatureSetFromComponents, ISignatureSet} from "@lodestar/state-transition"; diff --git a/packages/beacon-node/src/chain/validation/signatureSets/syncCommitteeContribution.ts b/packages/beacon-node/src/chain/validation/signatureSets/syncCommitteeContribution.ts index 71b7970716e9..0e1eaefef7c6 100644 --- a/packages/beacon-node/src/chain/validation/signatureSets/syncCommitteeContribution.ts +++ b/packages/beacon-node/src/chain/validation/signatureSets/syncCommitteeContribution.ts @@ -1,4 +1,4 @@ -import type {PublicKey} from "@chainsafe/bls/types"; +import {PublicKey} from "@chainsafe/blst"; import {altair, ssz} from "@lodestar/types"; import {DOMAIN_SYNC_COMMITTEE} from "@lodestar/params"; import {CachedBeaconStateAltair, computeSigningRoot, ISignatureSet, SignatureSetType} from "@lodestar/state-transition"; diff --git a/packages/beacon-node/src/eth1/provider/eth1Provider.ts b/packages/beacon-node/src/eth1/provider/eth1Provider.ts index 493884c0b5f0..3af909cd132e 100644 --- a/packages/beacon-node/src/eth1/provider/eth1Provider.ts +++ b/packages/beacon-node/src/eth1/provider/eth1Provider.ts @@ -1,7 +1,7 @@ import {toHexString} from "@chainsafe/ssz"; import {phase0} from "@lodestar/types"; import {ChainConfig} from "@lodestar/config"; -import {fromHex, isErrorAborted, createElapsedTimeTracker, toSafePrintableUrl} from "@lodestar/utils"; +import {fromHex, isErrorAborted, createElapsedTimeTracker, toPrintableUrl} from "@lodestar/utils"; import {Logger} from "@lodestar/logger"; import {FetchError, isFetchError} from "@lodestar/api"; @@ -84,7 +84,7 @@ export class Eth1Provider implements IEth1Provider { jwtVersion: opts.jwtVersion, metrics: metrics, }); - this.logger?.info("Eth1 provider", {urls: providerUrls.map(toSafePrintableUrl).toString()}); + this.logger?.info("Eth1 provider", {urls: providerUrls.map(toPrintableUrl).toString()}); this.rpc.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => { const oldState = this.state; diff --git a/packages/beacon-node/src/execution/builder/http.ts b/packages/beacon-node/src/execution/builder/http.ts index 9791f93a8ee2..934b874f5ae3 100644 --- a/packages/beacon-node/src/execution/builder/http.ts +++ b/packages/beacon-node/src/execution/builder/http.ts @@ -14,7 +14,7 @@ import {ChainForkConfig} from "@lodestar/config"; import {Logger} from "@lodestar/logger"; import {getClient, ApiClient as BuilderApi} from "@lodestar/api/builder"; import {SLOTS_PER_EPOCH, ForkExecution} from "@lodestar/params"; -import {toSafePrintableUrl} from "@lodestar/utils"; +import {toPrintableUrl} from "@lodestar/utils"; import {Metrics} from "../../metrics/metrics.js"; import {IExecutionBuilder} from "./interface.js"; @@ -64,7 +64,7 @@ export class ExecutionBuilderHttp implements IExecutionBuilder { }, {config, metrics: metrics?.builderHttpClient} ); - logger?.info("External builder", {url: toSafePrintableUrl(baseUrl)}); + logger?.info("External builder", {url: toPrintableUrl(baseUrl)}); this.config = config; this.issueLocalFcUWithFeeRecipient = opts.issueLocalFcUWithFeeRecipient; diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index c57d5f9b2dbc..c64a9715589f 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -12,6 +12,7 @@ import {Metrics} from "../../metrics/index.js"; import {JobItemQueue} from "../../util/queue/index.js"; import {EPOCHS_PER_BATCH} from "../../sync/constants.js"; import {numToQuantity} from "../../eth1/provider/utils.js"; +import {getLodestarClientVersion} from "../../util/metadata.js"; import { ExecutionPayloadStatus, ExecutePayloadResponse, @@ -21,6 +22,8 @@ import { BlobsBundle, VersionedHashes, ExecutionEngineState, + ClientVersion, + ClientCode, } from "./interface.js"; import {PayloadIdCache} from "./payloadIdCache.js"; import { @@ -63,6 +66,14 @@ export type ExecutionEngineHttpOpts = { * A version string that will be set in `clv` field of jwt claims */ jwtVersion?: string; + /** + * Lodestar version to be used for `ClientVersion` + */ + version?: string; + /** + * Lodestar commit to be used for `ClientVersion` + */ + commit?: string; }; export const defaultExecutionEngineHttpOpts: ExecutionEngineHttpOpts = { @@ -105,6 +116,9 @@ export class ExecutionEngineHttp implements IExecutionEngine { // It's safer to to avoid false positives and assume that the EL is syncing until we receive the first payload state: ExecutionEngineState = ExecutionEngineState.ONLINE; + /** Cached EL client version from the latest getClientVersion call */ + clientVersion?: ClientVersion | null; + readonly payloadIdCache = new PayloadIdCache(); /** * A queue to serialize the fcUs and newPayloads calls: @@ -126,7 +140,8 @@ export class ExecutionEngineHttp implements IExecutionEngine { constructor( private readonly rpc: IJsonRpcHttpClient, - {metrics, signal, logger}: ExecutionEngineModules + {metrics, signal, logger}: ExecutionEngineModules, + private readonly opts?: ExecutionEngineHttpOpts ) { this.rpcFetchQueue = new JobItemQueue<[EngineRequest], EngineResponse>( this.jobQueueProcessor, @@ -140,6 +155,13 @@ export class ExecutionEngineHttp implements IExecutionEngine { }); this.rpc.emitter.on(JsonRpcHttpClientEvent.RESPONSE, () => { + if (this.clientVersion === undefined) { + this.clientVersion = null; + // This statement should only be called first time receiving response after startup + this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => { + this.logger.debug("Unable to get execution client version", {}, e); + }); + } this.updateEngineState(getExecutionEngineState({targetState: ExecutionEngineState.ONLINE, oldState: this.state})); }); } @@ -417,6 +439,29 @@ export class ExecutionEngineHttp implements IExecutionEngine { return response.map(deserializeExecutionPayloadBody); } + private async getClientVersion(clientVersion: ClientVersion): Promise { + const method = "engine_getClientVersionV1"; + + const response = await this.rpc.fetchWithRetries< + EngineApiRpcReturnTypes[typeof method], + EngineApiRpcParamTypes[typeof method] + >({method, params: [clientVersion]}); + + const clientVersions = response.map((cv) => { + const code = cv.code in ClientCode ? ClientCode[cv.code as keyof typeof ClientCode] : ClientCode.XX; + return {code, name: cv.name, version: cv.version, commit: cv.commit}; + }); + + if (clientVersions.length === 0) { + throw Error("Received empty client versions array"); + } + + this.clientVersion = clientVersions[0]; + this.logger.debug("Execution client version updated", this.clientVersion); + + return clientVersions; + } + private updateEngineState(newState: ExecutionEngineState): void { const oldState = this.state; @@ -425,6 +470,10 @@ export class ExecutionEngineHttp implements IExecutionEngine { switch (newState) { case ExecutionEngineState.ONLINE: this.logger.info("Execution client became online", {oldState, newState}); + this.getClientVersion(getLodestarClientVersion(this.opts)).catch((e) => { + this.logger.debug("Unable to get execution client version", {}, e); + this.clientVersion = null; + }); break; case ExecutionEngineState.OFFLINE: this.logger.error("Execution client went offline", {oldState, newState}); diff --git a/packages/beacon-node/src/execution/engine/index.ts b/packages/beacon-node/src/execution/engine/index.ts index 2d92a439c86d..a8b878502aff 100644 --- a/packages/beacon-node/src/execution/engine/index.ts +++ b/packages/beacon-node/src/execution/engine/index.ts @@ -1,4 +1,4 @@ -import {fromHex, toSafePrintableUrl} from "@lodestar/utils"; +import {fromHex, toPrintableUrl} from "@lodestar/utils"; import {JsonRpcHttpClient} from "../../eth1/provider/jsonRpcHttpClient.js"; import {IExecutionEngine} from "./interface.js"; import {ExecutionEngineDisabled} from "./disabled.js"; @@ -39,8 +39,8 @@ export function getExecutionEngineHttp( jwtId: opts.jwtId, jwtVersion: opts.jwtVersion, }); - modules.logger.info("Execution client", {urls: opts.urls.map(toSafePrintableUrl).toString()}); - return new ExecutionEngineHttp(rpc, modules); + modules.logger.info("Execution client", {urls: opts.urls.map(toPrintableUrl).toString()}); + return new ExecutionEngineHttp(rpc, modules, opts); } export function initializeExecutionEngine( diff --git a/packages/beacon-node/src/execution/engine/interface.ts b/packages/beacon-node/src/execution/engine/interface.ts index 13575f01bce1..fa1da210cd12 100644 --- a/packages/beacon-node/src/execution/engine/interface.ts +++ b/packages/beacon-node/src/execution/engine/interface.ts @@ -38,6 +38,26 @@ export enum ExecutionEngineState { AUTH_FAILED = "AUTH_FAILED", } +/** + * Client code as defined in https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.4/src/engine/identification.md#clientcode + * ClientCode.XX is dedicated to other clients which do not have their own code + */ +export enum ClientCode { + BU = "BU", // besu + EJ = "EJ", // ethereumJS + EG = "EG", // erigon + GE = "GE", // go-ethereum + GR = "GR", // grandine + LH = "LH", // lighthouse + LS = "LS", // lodestar + NM = "NM", // nethermind + NB = "NB", // nimbus + TK = "TK", // teku + PM = "PM", // prysm + RH = "RH", // reth + XX = "XX", // unknown +} + export type ExecutePayloadResponse = | { status: ExecutionPayloadStatus.SYNCING | ExecutionPayloadStatus.ACCEPTED; @@ -80,6 +100,13 @@ export type BlobsBundle = { proofs: KZGProof[]; }; +export type ClientVersion = { + code: ClientCode; + name: string; + version: string; + commit: string; +}; + export type VersionedHashes = Uint8Array[]; /** @@ -91,6 +118,8 @@ export type VersionedHashes = Uint8Array[]; export interface IExecutionEngine { readonly state: ExecutionEngineState; + readonly clientVersion?: ClientVersion | null; + payloadIdCache: PayloadIdCache; /** * A state transition function which applies changes to the self.execution_state. diff --git a/packages/beacon-node/src/execution/engine/mock.ts b/packages/beacon-node/src/execution/engine/mock.ts index 5779713435a5..a99a76508df8 100644 --- a/packages/beacon-node/src/execution/engine/mock.ts +++ b/packages/beacon-node/src/execution/engine/mock.ts @@ -24,7 +24,7 @@ import { BlobsBundleRpc, ExecutionPayloadBodyRpc, } from "./types.js"; -import {ExecutionPayloadStatus, PayloadIdCache} from "./interface.js"; +import {ClientCode, ExecutionPayloadStatus, PayloadIdCache} from "./interface.js"; import {JsonRpcBackend} from "./utils.js"; const INTEROP_GAS_LIMIT = 30e6; @@ -96,6 +96,7 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { engine_getPayloadV3: this.getPayload.bind(this), engine_getPayloadBodiesByHashV1: this.getPayloadBodiesByHash.bind(this), engine_getPayloadBodiesByRangeV1: this.getPayloadBodiesByRange.bind(this), + engine_getClientVersionV1: this.getClientVersionV1.bind(this), }; } @@ -386,6 +387,12 @@ export class ExecutionEngineMockBackend implements JsonRpcBackend { return payload.executionPayload; } + private getClientVersionV1( + _clientVersion: EngineApiRpcParamTypes["engine_getClientVersionV1"][0] + ): EngineApiRpcReturnTypes["engine_getClientVersionV1"] { + return [{code: ClientCode.XX, name: "mock", version: "", commit: ""}]; + } + private timestampToFork(timestamp: number): ForkExecution { if (timestamp > (this.opts.denebForkTimestamp ?? Infinity)) return ForkName.deneb; if (timestamp > (this.opts.capellaForkTimestamp ?? Infinity)) return ForkName.capella; diff --git a/packages/beacon-node/src/execution/engine/types.ts b/packages/beacon-node/src/execution/engine/types.ts index 9fe9a990f76d..85f514c953b0 100644 --- a/packages/beacon-node/src/execution/engine/types.ts +++ b/packages/beacon-node/src/execution/engine/types.ts @@ -62,6 +62,11 @@ export type EngineApiRpcParamTypes = { * 2. count: QUANTITY, 64 bits - Number of blocks to return */ engine_getPayloadBodiesByRangeV1: [start: QUANTITY, count: QUANTITY]; + + /** + * Object - Instance of ClientVersion + */ + engine_getClientVersionV1: [ClientVersionRpc]; }; export type PayloadStatus = { @@ -100,6 +105,8 @@ export type EngineApiRpcReturnTypes = { engine_getPayloadBodiesByHashV1: (ExecutionPayloadBodyRpc | null)[]; engine_getPayloadBodiesByRangeV1: (ExecutionPayloadBodyRpc | null)[]; + + engine_getClientVersionV1: ClientVersionRpc[]; }; type ExecutionPayloadRpcWithValue = { @@ -157,6 +164,17 @@ export type PayloadAttributesRpc = { parentBeaconBlockRoot?: DATA; }; +export type ClientVersionRpc = { + /** ClientCode */ + code: string; + /** string, Human-readable name of the client */ + name: string; + /** string, the version string of the current implementation */ + version: string; + /** DATA, 4 bytes - first four bytes of the latest commit hash of this build */ + commit: DATA; +}; + export interface BlobsBundleRpc { commitments: DATA[]; // each 48 bytes blobs: DATA[]; // each 4096 * 32 = 131072 bytes diff --git a/packages/beacon-node/src/metrics/metrics/lodestar.ts b/packages/beacon-node/src/metrics/metrics/lodestar.ts index 5ebdbac48959..f43a3f1cdbe6 100644 --- a/packages/beacon-node/src/metrics/metrics/lodestar.ts +++ b/packages/beacon-node/src/metrics/metrics/lodestar.ts @@ -482,9 +482,9 @@ export function createLodestarMetrics( name: "lodestar_bls_thread_pool_batchable_sig_sets_total", help: "Count of total batchable signature sets", }), - signatureDeserializationMainThreadDuration: register.histogram({ - name: "lodestar_bls_thread_pool_signature_deserialization_main_thread_time_seconds", - help: "Total time spent deserializing signatures on main thread", + aggregateWithRandomnessMainThreadDuration: register.histogram({ + name: "lodestar_bls_thread_pool_aggregate_with_randomness_main_thread_time_seconds", + help: "Total time performing aggregateWithRandomness on main thread", buckets: [0.001, 0.005, 0.01, 0.1], }), pubkeysAggregationMainThreadDuration: register.histogram({ diff --git a/packages/beacon-node/src/metrics/server/http.ts b/packages/beacon-node/src/metrics/server/http.ts index d8fbb289e951..e197dcfa2a25 100644 --- a/packages/beacon-node/src/metrics/server/http.ts +++ b/packages/beacon-node/src/metrics/server/http.ts @@ -24,9 +24,9 @@ export async function getHttpMetricsServer( opts: HttpMetricsServerOpts, { register, - getOtherMetrics = async () => "", + getOtherMetrics = async () => [], logger, - }: {register: Registry; getOtherMetrics?: () => Promise; logger: Logger} + }: {register: Registry; getOtherMetrics?: () => Promise; logger: Logger} ): Promise { // New registry to metric the metrics. Using the same registry would deadlock the .metrics promise const httpServerRegister = new RegistryMetricCreator(); @@ -53,7 +53,8 @@ export async function getHttpMetricsServer( } else { // Get scrape time metrics const httpServerMetrics = await httpServerRegister.metrics(); - const metricsStr = `${metricsRes[0].result}\n\n${metricsRes[1]}\n\n${httpServerMetrics}`; + const metrics = [metricsRes[0].result, httpServerMetrics, ...metricsRes[1]]; + const metricsStr = metrics.join("\n\n"); res.writeHead(200, {"content-type": register.contentType}).end(metricsStr); } } else { diff --git a/packages/beacon-node/src/metrics/validatorMonitor.ts b/packages/beacon-node/src/metrics/validatorMonitor.ts index a9d783786e88..e5c8eef83679 100644 --- a/packages/beacon-node/src/metrics/validatorMonitor.ts +++ b/packages/beacon-node/src/metrics/validatorMonitor.ts @@ -1,6 +1,5 @@ import { computeEpochAtSlot, - AttesterStatus, parseAttesterFlags, CachedBeaconStateAllForks, CachedBeaconStateAltair, @@ -39,7 +38,14 @@ export enum OpSource { export type ValidatorMonitor = { registerLocalValidator(index: number): void; registerLocalValidatorInSyncCommittee(index: number, untilEpoch: Epoch): void; - registerValidatorStatuses(currentEpoch: Epoch, statuses: AttesterStatus[], balances?: number[]): void; + registerValidatorStatuses( + currentEpoch: Epoch, + inclusionDelays: number[], + flags: number[], + isActiveCurrEpoch: boolean[], + isActivePrevEpoch: boolean[], + balances?: number[] + ): void; registerBeaconBlock(src: OpSource, seenTimestampSec: Seconds, block: BeaconBlock): void; registerBlobSidecar(src: OpSource, seenTimestampSec: Seconds, blob: deneb.BlobSidecar): void; registerImportedBlock(block: BeaconBlock, data: {proposerBalanceDelta: number}): void; @@ -115,12 +121,17 @@ type ValidatorStatus = { inclusionDistance: number; }; -function statusToSummary(status: AttesterStatus): ValidatorStatus { - const flags = parseAttesterFlags(status.flags); +function statusToSummary( + inclusionDelay: number, + flag: number, + isActiveInCurrentEpoch: boolean, + isActiveInPreviousEpoch: boolean +): ValidatorStatus { + const flags = parseAttesterFlags(flag); return { isSlashed: flags.unslashed, - isActiveInCurrentEpoch: status.active, - isActiveInPreviousEpoch: status.active, + isActiveInCurrentEpoch, + isActiveInPreviousEpoch, // TODO: Implement currentEpochEffectiveBalance: 0, @@ -130,7 +141,7 @@ function statusToSummary(status: AttesterStatus): ValidatorStatus { isCurrSourceAttester: flags.currSourceAttester, isCurrTargetAttester: flags.currTargetAttester, isCurrHeadAttester: flags.currHeadAttester, - inclusionDistance: status.inclusionDelay, + inclusionDistance: inclusionDelay, }; } @@ -287,7 +298,7 @@ export function createValidatorMonitor( } }, - registerValidatorStatuses(currentEpoch, statuses, balances) { + registerValidatorStatuses(currentEpoch, inclusionDelays, flags, isActiveCurrEpoch, isActiveInPrevEpoch, balances) { // Prevent registering status for the same epoch twice. processEpoch() may be ran more than once for the same epoch. if (currentEpoch <= lastRegisteredStatusEpoch) { return; @@ -301,12 +312,12 @@ export function createValidatorMonitor( // - One to account for it being the previous epoch. // - One to account for the state advancing an epoch whilst generating the validator // statuses. - const status = statuses[index]; - if (status === undefined) { - continue; - } - - const summary = statusToSummary(status); + const summary = statusToSummary( + inclusionDelays[index], + flags[index], + isActiveCurrEpoch[index], + isActiveInPrevEpoch[index] + ); if (summary.isPrevSourceAttester) { metrics.validatorMonitor.prevEpochOnChainSourceAttesterHit.inc(); diff --git a/packages/beacon-node/src/network/core/networkCore.ts b/packages/beacon-node/src/network/core/networkCore.ts index fdd2b681602f..07b346bc29e4 100644 --- a/packages/beacon-node/src/network/core/networkCore.ts +++ b/packages/beacon-node/src/network/core/networkCore.ts @@ -499,9 +499,12 @@ export class NetworkCore implements INetworkCore { private subscribeCoreTopicsAtFork(fork: ForkName): void { if (this.subscribedForks.has(fork)) return; this.subscribedForks.add(fork); - const {subscribeAllSubnets} = this.opts; + const {subscribeAllSubnets, disableLightClientServer} = this.opts; - for (const topic of getCoreTopicsAtFork(fork, {subscribeAllSubnets})) { + for (const topic of getCoreTopicsAtFork(fork, { + subscribeAllSubnets, + disableLightClientServer, + })) { this.gossip.subscribeTopic({...topic, fork}); } } @@ -509,9 +512,12 @@ export class NetworkCore implements INetworkCore { private unsubscribeCoreTopicsAtFork(fork: ForkName): void { if (!this.subscribedForks.has(fork)) return; this.subscribedForks.delete(fork); - const {subscribeAllSubnets} = this.opts; + const {subscribeAllSubnets, disableLightClientServer} = this.opts; - for (const topic of getCoreTopicsAtFork(fork, {subscribeAllSubnets})) { + for (const topic of getCoreTopicsAtFork(fork, { + subscribeAllSubnets, + disableLightClientServer, + })) { this.gossip.unsubscribeTopic({...topic, fork}); } } diff --git a/packages/beacon-node/src/network/gossip/gossipsub.ts b/packages/beacon-node/src/network/gossip/gossipsub.ts index 4066452f1e3b..e6977abe8ce6 100644 --- a/packages/beacon-node/src/network/gossip/gossipsub.ts +++ b/packages/beacon-node/src/network/gossip/gossipsub.ts @@ -56,6 +56,7 @@ export type Eth2GossipsubOpts = { gossipsubAwaitHandler?: boolean; disableFloodPublish?: boolean; skipParamsLog?: boolean; + disableLightClientServer?: boolean; }; /** @@ -124,7 +125,9 @@ export class Eth2Gossipsub extends GossipSub { isFinite(config.BELLATRIX_FORK_EPOCH) ? GOSSIP_MAX_SIZE_BELLATRIX : GOSSIP_MAX_SIZE ), metricsRegister: metricsRegister as MetricsRegister | null, - metricsTopicStrToLabel: metricsRegister ? getMetricsTopicStrToLabel(config) : undefined, + metricsTopicStrToLabel: metricsRegister + ? getMetricsTopicStrToLabel(config, {disableLightClientServer: opts.disableLightClientServer ?? false}) + : undefined, asyncValidation: true, maxOutboundBufferSize: MAX_OUTBOUND_BUFFER_SIZE, @@ -321,11 +324,14 @@ function attSubnetLabel(subnet: number): string { else return `0${subnet}`; } -function getMetricsTopicStrToLabel(config: BeaconConfig): TopicStrToLabel { +function getMetricsTopicStrToLabel(config: BeaconConfig, opts: {disableLightClientServer: boolean}): TopicStrToLabel { const metricsTopicStrToLabel = new Map(); for (const {name: fork} of config.forksAscendingEpochOrder) { - const topics = getCoreTopicsAtFork(fork, {subscribeAllSubnets: true}); + const topics = getCoreTopicsAtFork(fork, { + subscribeAllSubnets: true, + disableLightClientServer: opts.disableLightClientServer, + }); for (const topic of topics) { metricsTopicStrToLabel.set(stringifyGossipTopic(config, {...topic, fork}), topic.type); } diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 0f3f6942a10a..4923b71e6887 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -201,7 +201,7 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr: */ export function getCoreTopicsAtFork( fork: ForkName, - opts: {subscribeAllSubnets?: boolean} + opts: {subscribeAllSubnets?: boolean; disableLightClientServer?: boolean} ): GossipTopicTypeMap[keyof GossipTopicTypeMap][] { // Common topics for all forks const topics: GossipTopicTypeMap[keyof GossipTopicTypeMap][] = [ @@ -227,8 +227,10 @@ export function getCoreTopicsAtFork( // Any fork after altair included if (ForkSeq[fork] >= ForkSeq.altair) { topics.push({type: GossipType.sync_committee_contribution_and_proof}); - topics.push({type: GossipType.light_client_optimistic_update}); - topics.push({type: GossipType.light_client_finality_update}); + if (!opts.disableLightClientServer) { + topics.push({type: GossipType.light_client_optimistic_update}); + topics.push({type: GossipType.light_client_finality_update}); + } } if (opts.subscribeAllSubnets) { diff --git a/packages/beacon-node/src/network/options.ts b/packages/beacon-node/src/network/options.ts index 713ca4b21ad0..d2070873261b 100644 --- a/packages/beacon-node/src/network/options.ts +++ b/packages/beacon-node/src/network/options.ts @@ -8,11 +8,11 @@ import {SubnetsServiceOpts} from "./subnets/interface.js"; export interface NetworkOptions extends PeerManagerOpts, // remove all Functions - Omit, + Omit, NetworkProcessorOpts, PeerRpcScoreOpts, SubnetsServiceOpts, - Eth2GossipsubOpts { + Omit { localMultiaddrs: string[]; bootMultiaddrs?: string[]; subscribeAllSubnets?: boolean; @@ -22,6 +22,7 @@ export interface NetworkOptions private?: boolean; useWorker?: boolean; maxYoungGenerationSizeMb?: number; + disableLightClientServer?: boolean; } export const defaultNetworkOptions: NetworkOptions = { @@ -41,4 +42,6 @@ export const defaultNetworkOptions: NetworkOptions = { slotsToSubscribeBeforeAggregatorDuty: 2, // this should only be set to true if useWorker is true beaconAttestationBatchValidation: true, + // This will enable the light client server by default + disableLightClientServer: false, }; diff --git a/packages/beacon-node/src/network/peers/discover.ts b/packages/beacon-node/src/network/peers/discover.ts index 1cb084846f61..79603f780e3d 100644 --- a/packages/beacon-node/src/network/peers/discover.ts +++ b/packages/beacon-node/src/network/peers/discover.ts @@ -249,13 +249,21 @@ export class PeerDiscovery { } // Run a discv5 subnet query to try to discover new peers - if (subnetsToDiscoverPeers.length > 0 || cachedENRsToDial.size < peersToConnect) { + const shouldRunFindRandomNodeQuery = subnetsToDiscoverPeers.length > 0 || cachedENRsToDial.size < peersToConnect; + if (shouldRunFindRandomNodeQuery) { void this.runFindRandomNodeQuery(); } + + this.logger.debug("Discover peers outcome", { + peersToConnect, + peersAvailableToDial: cachedENRsToDial.size, + subnetsToDiscover: subnetsToDiscoverPeers.length, + shouldRunFindRandomNodeQuery, + }); } /** - * Request to find peers. First, looked at cached peers in peerStore + * Request discv5 to find peers if there is no query in progress */ private async runFindRandomNodeQuery(): Promise { // Delay the 1st query after starting discv5 @@ -305,6 +313,7 @@ export class PeerDiscovery { const attnets = zeroAttnets; const syncnets = zeroSyncnets; const status = this.handleDiscoveredPeer(id, multiaddrs[0], attnets, syncnets); + this.logger.debug("Discovered peer via libp2p", {peer: prettyPrintPeerId(id), status}); this.metrics?.discovery.discoveredStatus.inc({status}); }; @@ -336,6 +345,7 @@ export class PeerDiscovery { const syncnets = syncnetsBytes ? deserializeEnrSubnets(syncnetsBytes, SYNC_COMMITTEE_SUBNET_COUNT) : zeroSyncnets; const status = this.handleDiscoveredPeer(peerId, multiaddrTCP, attnets, syncnets); + this.logger.debug("Discovered peer via discv5", {peer: prettyPrintPeerId(peerId), status}); this.metrics?.discovery.discoveredStatus.inc({status}); }; diff --git a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts index 8d131e9e9945..d34b379f4ccd 100644 --- a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts +++ b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts @@ -51,7 +51,7 @@ export interface ReqRespBeaconNodeModules { getHandler: GetReqRespHandlerFn; } -export type ReqRespBeaconNodeOpts = ReqRespOpts; +export type ReqRespBeaconNodeOpts = ReqRespOpts & {disableLightClientServer?: boolean}; /** * Implementation of Ethereum Consensus p2p Req/Resp domain. @@ -72,6 +72,7 @@ export class ReqRespBeaconNode extends ReqResp { private readonly config: BeaconConfig; protected readonly logger: Logger; + protected readonly disableLightClientServer: boolean; constructor(modules: ReqRespBeaconNodeModules, options: ReqRespBeaconNodeOpts = {}) { const {events, peersData, peerRpcScores, metadata, metrics, logger} = modules; @@ -95,6 +96,7 @@ export class ReqRespBeaconNode extends ReqResp { } ); + this.disableLightClientServer = options.disableLightClientServer ?? false; this.peerRpcScores = peerRpcScores; this.peersData = peersData; this.config = modules.config; @@ -233,7 +235,7 @@ export class ReqRespBeaconNode extends ReqResp { ); } - if (ForkSeq[fork] >= ForkSeq.altair) { + if (ForkSeq[fork] >= ForkSeq.altair && !this.disableLightClientServer) { // Should be okay to enable before altair, but for consistency only enable afterwards protocolsAtFork.push( [protocols.LightClientBootstrap(this.config), this.getHandler(ReqRespMethod.LightClientBootstrap)], diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts index 06277eb533a3..d14e0945e977 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientBootstrap.ts @@ -8,8 +8,11 @@ import { import {Root} from "@lodestar/types"; import {IBeaconChain} from "../../../chain/index.js"; import {ReqRespMethod, responseSszTypeByMethod} from "../types.js"; +import {assertLightClientServer} from "../../../node/utils/lightclient.js"; export async function* onLightClientBootstrap(requestBody: Root, chain: IBeaconChain): AsyncIterable { + assertLightClientServer(chain.lightClientServer); + try { const bootstrap = await chain.lightClientServer.getBootstrap(requestBody); const fork = chain.config.getForkName(bootstrap.header.beacon.slot); diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts index 064f9f6ef4e3..2468b0b64f4b 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientFinalityUpdate.ts @@ -1,8 +1,11 @@ import {ResponseOutgoing, RespStatus, ResponseError} from "@lodestar/reqresp"; import {IBeaconChain} from "../../../chain/index.js"; import {ReqRespMethod, responseSszTypeByMethod} from "../types.js"; +import {assertLightClientServer} from "../../../node/utils/lightclient.js"; export async function* onLightClientFinalityUpdate(chain: IBeaconChain): AsyncIterable { + assertLightClientServer(chain.lightClientServer); + const update = chain.lightClientServer.getFinalityUpdate(); if (update === null) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest finality update available"); diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts index 16001024573e..ba8371910c02 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientOptimisticUpdate.ts @@ -1,8 +1,11 @@ import {ResponseOutgoing, ResponseError, RespStatus} from "@lodestar/reqresp"; import {IBeaconChain} from "../../../chain/index.js"; import {ReqRespMethod, responseSszTypeByMethod} from "../types.js"; +import {assertLightClientServer} from "../../../node/utils/lightclient.js"; export async function* onLightClientOptimisticUpdate(chain: IBeaconChain): AsyncIterable { + assertLightClientServer(chain.lightClientServer); + const update = chain.lightClientServer.getOptimisticUpdate(); if (update === null) { throw new ResponseError(RespStatus.RESOURCE_UNAVAILABLE, "No latest optimistic update available"); diff --git a/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts b/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts index 8dd7e36142cc..eb0e3c3d3f4e 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/lightClientUpdatesByRange.ts @@ -9,11 +9,14 @@ import { } from "@lodestar/reqresp"; import {IBeaconChain} from "../../../chain/index.js"; import {ReqRespMethod, responseSszTypeByMethod} from "../types.js"; +import {assertLightClientServer} from "../../../node/utils/lightclient.js"; export async function* onLightClientUpdatesByRange( requestBody: altair.LightClientUpdatesByRange, chain: IBeaconChain ): AsyncIterable { + assertLightClientServer(chain.lightClientServer); + const count = Math.min(MAX_REQUEST_LIGHT_CLIENT_UPDATES, requestBody.count); for (let period = requestBody.startPeriod; period < requestBody.startPeriod + count; period++) { try { diff --git a/packages/beacon-node/src/node/nodejs.ts b/packages/beacon-node/src/node/nodejs.ts index a1147b60bea2..088541a6b5d5 100644 --- a/packages/beacon-node/src/node/nodejs.ts +++ b/packages/beacon-node/src/node/nodejs.ts @@ -21,6 +21,7 @@ import {getApi, BeaconRestApiServer} from "../api/index.js"; import {initializeExecutionEngine, initializeExecutionBuilder} from "../execution/index.js"; import {initializeEth1ForBlockProduction} from "../eth1/index.js"; import {initCKZG, loadEthereumTrustedSetup, TrustedFileMode} from "../util/kzg.js"; +import {HistoricalStateRegen} from "../chain/historicalState/index.js"; import {IBeaconNodeOptions} from "./options.js"; import {runNodeNotifier} from "./notifier.js"; @@ -194,6 +195,17 @@ export class BeaconNode { ) : null; + const historicalStateRegen = await HistoricalStateRegen.init({ + opts: { + genesisTime: anchorState.genesisTime, + dbLocation: opts.db.name, + }, + config, + metrics, + logger: logger.child({module: LoggerModule.chain}), + signal, + }); + const chain = new BeaconChain(opts.chain, { config, db, @@ -216,6 +228,7 @@ export class BeaconNode { executionBuilder: opts.executionBuilder.enabled ? initializeExecutionBuilder(opts.executionBuilder, config, metrics, logger) : undefined, + historicalStateRegen, }); // Load persisted data from disk to in-memory caches @@ -274,7 +287,7 @@ export class BeaconNode { const metricsServer = opts.metrics.enabled ? await getHttpMetricsServer(opts.metrics, { register: (metrics as Metrics).register, - getOtherMetrics: () => network.scrapeMetrics(), + getOtherMetrics: async () => Promise.all([network.scrapeMetrics(), historicalStateRegen.scrapeMetrics()]), logger: logger.child({module: LoggerModule.metrics}), }) : null; diff --git a/packages/beacon-node/src/node/utils/lightclient.ts b/packages/beacon-node/src/node/utils/lightclient.ts new file mode 100644 index 000000000000..99d7b587e06f --- /dev/null +++ b/packages/beacon-node/src/node/utils/lightclient.ts @@ -0,0 +1,7 @@ +import {LightClientServer} from "../../chain/lightClient/index.js"; + +export function assertLightClientServer(server: LightClientServer | undefined): asserts server is LightClientServer { + if (!server) { + throw Error("Light client server is disabled"); + } +} diff --git a/packages/beacon-node/src/sync/sync.ts b/packages/beacon-node/src/sync/sync.ts index c7f01e1eae78..cc8ddc6eb499 100644 --- a/packages/beacon-node/src/sync/sync.ts +++ b/packages/beacon-node/src/sync/sync.ts @@ -93,8 +93,8 @@ export class BeaconSync implements IBeaconSync { // If we are pre/at genesis, signal ready if (currentSlot <= GENESIS_SLOT) { return { - headSlot: "0", - syncDistance: "0", + headSlot: 0, + syncDistance: 0, isSyncing: false, isOptimistic: false, elOffline, @@ -107,16 +107,16 @@ export class BeaconSync implements IBeaconSync { case SyncState.SyncingHead: case SyncState.Stalled: return { - headSlot: String(head.slot), - syncDistance: String(currentSlot - head.slot), + headSlot: head.slot, + syncDistance: currentSlot - head.slot, isSyncing: true, isOptimistic: isOptimisticBlock(head), elOffline, }; case SyncState.Synced: return { - headSlot: String(head.slot), - syncDistance: "0", + headSlot: head.slot, + syncDistance: 0, isSyncing: false, isOptimistic: isOptimisticBlock(head), elOffline, diff --git a/packages/beacon-node/src/util/graffiti.ts b/packages/beacon-node/src/util/graffiti.ts index 514be4aa5bdd..9a4bc3d9689b 100644 --- a/packages/beacon-node/src/util/graffiti.ts +++ b/packages/beacon-node/src/util/graffiti.ts @@ -1,4 +1,5 @@ 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 @@ -6,3 +7,23 @@ import {GRAFFITI_SIZE} from "../constants/index.js"; export function toGraffitiBuffer(graffiti: string): Buffer { return Buffer.concat([Buffer.from(graffiti, "utf8"), Buffer.alloc(GRAFFITI_SIZE, 0)], GRAFFITI_SIZE); } + +export function getDefaultGraffiti( + consensusClientVersion: ClientVersion, + executionClientVersion: ClientVersion | null | undefined, + opts: {private?: boolean} +): string { + if (opts.private) { + return ""; + } + + if (executionClientVersion != null) { + const {code: executionCode, commit: executionCommit} = executionClientVersion; + + // Follow the 2-byte commit format in https://github.com/ethereum/execution-apis/pull/517#issuecomment-1918512560 + return `${executionCode}${executionCommit.slice(0, 4)}${consensusClientVersion.code}${consensusClientVersion.commit.slice(0, 4)}`; + } + + // No EL client info available. We still want to include CL info albeit not spec compliant + return `${consensusClientVersion.code}${consensusClientVersion.commit.slice(0, 4)}`; +} diff --git a/packages/beacon-node/src/util/metadata.ts b/packages/beacon-node/src/util/metadata.ts new file mode 100644 index 000000000000..9e6d20710105 --- /dev/null +++ b/packages/beacon-node/src/util/metadata.ts @@ -0,0 +1,10 @@ +import {ClientCode, ClientVersion} from "../execution/index.js"; + +export function getLodestarClientVersion(info?: {version?: string; commit?: string}): ClientVersion { + return { + code: ClientCode.LS, + name: "Lodestar", + version: info?.version ?? "", + commit: info?.commit?.slice(0, 8) ?? "", + }; +} diff --git a/packages/beacon-node/src/util/sszBytes.ts b/packages/beacon-node/src/util/sszBytes.ts index 802b9a266ab1..f87b899e9591 100644 --- a/packages/beacon-node/src/util/sszBytes.ts +++ b/packages/beacon-node/src/util/sszBytes.ts @@ -198,11 +198,24 @@ export function getSlotFromBlobSidecarSerialized(data: Uint8Array): Slot | null return getSlotFromOffset(data, SLOT_BYTES_POSITION_IN_SIGNED_BLOB_SIDECAR); } -function getSlotFromOffset(data: Uint8Array, offset: number): Slot { - // TODO: Optimize - const dv = new DataView(data.buffer, data.byteOffset, data.byteLength); - // Read only the first 4 bytes of Slot, max value is 4,294,967,295 will be reached 1634 years after genesis - return dv.getUint32(offset, true); +/** + * Read only the first 4 bytes of Slot, max value is 4,294,967,295 will be reached 1634 years after genesis + * + * If the high bytes are not zero, return null + */ +function getSlotFromOffset(data: Uint8Array, offset: number): Slot | null { + return checkSlotHighBytes(data, offset) ? getSlotFromOffsetTrusted(data, offset) : null; +} + +/** + * Read only the first 4 bytes of Slot, max value is 4,294,967,295 will be reached 1634 years after genesis + */ +function getSlotFromOffsetTrusted(data: Uint8Array, offset: number): Slot { + return (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)) >>> 0; +} + +function checkSlotHighBytes(data: Uint8Array, offset: number): boolean { + return (data[offset + 4] | data[offset + 5] | data[offset + 6] | data[offset + 7]) === 0; } function toBase64(data: Uint8Array): string { diff --git a/packages/beacon-node/test/e2e/api/impl/beacon/node/endpoints.test.ts b/packages/beacon-node/test/e2e/api/impl/beacon/node/endpoints.test.ts index d85fdb80720f..2d2a0a37c59e 100644 --- a/packages/beacon-node/test/e2e/api/impl/beacon/node/endpoints.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/beacon/node/endpoints.test.ts @@ -1,6 +1,7 @@ import {describe, beforeAll, afterAll, it, expect, vi} from "vitest"; import {createBeaconConfig} from "@lodestar/config"; import {chainConfig as chainConfigDef} from "@lodestar/config/default"; +import {routes} from "@lodestar/api"; import {ApiClient, getClient} from "@lodestar/api/beacon"; import {sleep} from "@lodestar/utils"; import {LogLevel, testLogger} from "../../../../../utils/logger.js"; @@ -46,9 +47,9 @@ describe("beacon node api", function () { it("should return valid syncing status", async () => { const res = await client.node.getSyncingStatus(); - expect(res.value()).toEqual({ - headSlot: "0", - syncDistance: "0", + expect(res.value()).toEqual({ + headSlot: 0, + syncDistance: 0, isSyncing: false, isOptimistic: false, elOffline: false, diff --git a/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts b/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts index afb4863dbe44..81154005af68 100644 --- a/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/lightclient/endpoint.test.ts @@ -1,5 +1,5 @@ import {describe, it, beforeEach, afterEach, expect} from "vitest"; -import bls from "@chainsafe/bls"; +import {aggregateSerializedPublicKeys} from "@chainsafe/blst"; import {createBeaconConfig, ChainConfig} from "@lodestar/config"; import {chainConfig as chainConfigDef} from "@lodestar/config/default"; import {getClient, HttpHeader, routes} from "@lodestar/api"; @@ -129,7 +129,7 @@ describe("lightclient api", function () { const committeePubkeys = Array.from({length: SYNC_COMMITTEE_SIZE}, (_, i) => i % 2 === 0 ? pubkeys[0] : pubkeys[1] ); - const aggregatePubkey = bls.aggregatePublicKeys(committeePubkeys); + const aggregatePubkey = aggregateSerializedPublicKeys(committeePubkeys).toBytes(); // single committee hash since we requested for the first period expect(committeeRes.value()).toEqual([ ssz.altair.SyncCommittee.hashTreeRoot({ diff --git a/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts b/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts index bf1e73469433..25f0d5133bcd 100644 --- a/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts +++ b/packages/beacon-node/test/e2e/chain/bls/multithread.test.ts @@ -1,6 +1,5 @@ import {describe, it, beforeAll, expect, beforeEach, afterEach} from "vitest"; -import bls from "@chainsafe/bls"; -import {PublicKey} from "@chainsafe/bls/types"; +import {PublicKey, SecretKey} from "@chainsafe/blst"; import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition"; import {BlsMultiThreadWorkerPool} from "../../../../src/chain/bls/multithread/index.js"; import {testLogger} from "../../../utils/logger.js"; @@ -29,7 +28,7 @@ describe("chain / bls / multithread queue", function () { beforeAll(() => { for (let i = 0; i < 3; i++) { - const sk = bls.SecretKey.fromBytes(Buffer.alloc(32, i + 1)); + const sk = SecretKey.fromBytes(Buffer.alloc(32, i + 1)); const msg = Buffer.alloc(32, i + 1); const pk = sk.toPublicKey(); const sig = sk.sign(msg); diff --git a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts index 145f378935fe..594e20ec4cb0 100644 --- a/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts +++ b/packages/beacon-node/test/e2e/chain/proposerBoostReorg.test.ts @@ -1,4 +1,4 @@ -import {describe, it, afterEach, expect} from "vitest"; +import {describe, it, afterEach, expect, vi} from "vitest"; import {SLOTS_PER_EPOCH} from "@lodestar/params"; import {TimestampFormatCode} from "@lodestar/logger"; import {ChainConfig} from "@lodestar/config"; @@ -12,125 +12,122 @@ import {getAndInitDevValidators} from "../../utils/node/validator.js"; import {waitForEvent} from "../../utils/events/resolver.js"; import {ReorgEventData} from "../../../src/chain/emitter.js"; -describe( - "proposer boost reorg", - function () { - const validatorCount = 8; - const testParams: Pick = - { - // eslint-disable-next-line @typescript-eslint/naming-convention - SECONDS_PER_SLOT: 2, - // need this to make block `reorgSlot - 1` strong enough - // eslint-disable-next-line @typescript-eslint/naming-convention - REORG_PARENT_WEIGHT_THRESHOLD: 80, - // need this to make block `reorgSlot + 1` to become the head - // eslint-disable-next-line @typescript-eslint/naming-convention - PROPOSER_SCORE_BOOST: 120, - }; +describe("proposer boost reorg", function () { + vi.setConfig({testTimeout: 60000}); - const afterEachCallbacks: (() => Promise | void)[] = []; - afterEach(async () => { - while (afterEachCallbacks.length > 0) { - const callback = afterEachCallbacks.pop(); - if (callback) await callback(); - } - }); + const validatorCount = 8; + const testParams: Pick = { + // eslint-disable-next-line @typescript-eslint/naming-convention + SECONDS_PER_SLOT: 2, + // need this to make block `reorgSlot - 1` strong enough + // eslint-disable-next-line @typescript-eslint/naming-convention + REORG_PARENT_WEIGHT_THRESHOLD: 80, + // need this to make block `reorgSlot + 1` to become the head + // eslint-disable-next-line @typescript-eslint/naming-convention + PROPOSER_SCORE_BOOST: 120, + }; - const reorgSlot = 10; - const proposerBoostReorg = true; - /** - * reorgSlot - * / - * reorgSlot - 1 ------------ reorgSlot + 1 - * - * Note that in addition of being not timely, there are other criterion that - * the block needs to satisfy before being re-orged out. This test assumes - * other criterion are already satisfied - */ - it(`should reorg a late block at slot ${reorgSlot}`, async () => { - // the node needs time to transpile/initialize bls worker threads - const genesisSlotsDelay = 7; - const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; - const testLoggerOpts: TestLoggerOpts = { - level: LogLevel.debug, - timestampFormat: { - format: TimestampFormatCode.EpochSlot, - genesisTime, - slotsPerEpoch: SLOTS_PER_EPOCH, - secondsPerSlot: testParams.SECONDS_PER_SLOT, - }, - }; - const logger = testLogger("BeaconNode", testLoggerOpts); - const bn = await getDevBeaconNode({ - params: testParams, - options: { - sync: {isSingleNode: true}, - network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false}, - chain: { - blsVerifyAllMainThread: true, - forkchoiceConstructor: TimelinessForkChoice, - proposerBoost: true, - proposerBoostReorg, - }, - }, - validatorCount, + const afterEachCallbacks: (() => Promise | void)[] = []; + afterEach(async () => { + while (afterEachCallbacks.length > 0) { + const callback = afterEachCallbacks.pop(); + if (callback) await callback(); + } + }); + + const reorgSlot = 10; + const proposerBoostReorg = true; + /** + * reorgSlot + * / + * reorgSlot - 1 ------------ reorgSlot + 1 + * + * Note that in addition of being not timely, there are other criterion that + * the block needs to satisfy before being re-orged out. This test assumes + * other criterion are already satisfied + */ + it(`should reorg a late block at slot ${reorgSlot}`, async () => { + // the node needs time to transpile/initialize bls worker threads + const genesisSlotsDelay = 7; + const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; + const testLoggerOpts: TestLoggerOpts = { + level: LogLevel.debug, + timestampFormat: { + format: TimestampFormatCode.EpochSlot, genesisTime, - logger, - }); + slotsPerEpoch: SLOTS_PER_EPOCH, + secondsPerSlot: testParams.SECONDS_PER_SLOT, + }, + }; + const logger = testLogger("BeaconNode", testLoggerOpts); + const bn = await getDevBeaconNode({ + params: testParams, + options: { + sync: {isSingleNode: true}, + network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false}, + chain: { + blsVerifyAllMainThread: true, + forkchoiceConstructor: TimelinessForkChoice, + proposerBoost: true, + proposerBoostReorg, + }, + }, + validatorCount, + genesisTime, + logger, + }); - (bn.chain.forkChoice as TimelinessForkChoice).lateSlot = reorgSlot; - afterEachCallbacks.push(async () => bn.close()); - const {validators} = await getAndInitDevValidators({ - node: bn, - logPrefix: "vc-0", - validatorsPerClient: validatorCount, - validatorClientCount: 1, - startIndex: 0, - useRestApi: false, - testLoggerOpts, - }); - afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close()))); + (bn.chain.forkChoice as TimelinessForkChoice).lateSlot = reorgSlot; + afterEachCallbacks.push(async () => bn.close()); + const {validators} = await getAndInitDevValidators({ + node: bn, + logPrefix: "vc-0", + validatorsPerClient: validatorCount, + validatorClientCount: 1, + startIndex: 0, + useRestApi: false, + testLoggerOpts, + }); + afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close()))); - const commonAncestor = await waitForEvent<{slot: Slot; block: RootHex}>( - bn.chain.emitter, - routes.events.EventType.head, - 240000, - ({slot}) => slot === reorgSlot - 1 - ); - // reorgSlot - // / - // commonAncestor ------------ newBlock - const commonAncestorRoot = commonAncestor.block; - const reorgBlockEventData = await waitForEvent<{slot: Slot; block: RootHex}>( + const commonAncestor = await waitForEvent<{slot: Slot; block: RootHex}>( + bn.chain.emitter, + routes.events.EventType.head, + 240000, + ({slot}) => slot === reorgSlot - 1 + ); + // reorgSlot + // / + // commonAncestor ------------ newBlock + const commonAncestorRoot = commonAncestor.block; + const reorgBlockEventData = await waitForEvent<{slot: Slot; block: RootHex}>( + bn.chain.emitter, + routes.events.EventType.head, + 240000, + ({slot}) => slot === reorgSlot + ); + const reorgBlockRoot = reorgBlockEventData.block; + const [newBlockEventData, reorgEventData] = await Promise.all([ + waitForEvent<{slot: Slot; block: RootHex}>( bn.chain.emitter, - routes.events.EventType.head, + routes.events.EventType.block, 240000, - ({slot}) => slot === reorgSlot - ); - const reorgBlockRoot = reorgBlockEventData.block; - const [newBlockEventData, reorgEventData] = await Promise.all([ - waitForEvent<{slot: Slot; block: RootHex}>( - bn.chain.emitter, - routes.events.EventType.block, - 240000, - ({slot}) => slot === reorgSlot + 1 - ), - waitForEvent(bn.chain.emitter, routes.events.EventType.chainReorg, 240000), - ]); - expect(reorgEventData.slot).toEqual(reorgSlot + 1); - const newBlock = await bn.chain.getBlockByRoot(newBlockEventData.block); - if (newBlock == null) { - throw Error(`Block ${reorgSlot + 1} not found`); - } - expect(reorgEventData.oldHeadBlock).toEqual(reorgBlockRoot); - expect(reorgEventData.newHeadBlock).toEqual(newBlockEventData.block); - expect(reorgEventData.depth).toEqual(2); - expect(toHexString(newBlock?.block.message.parentRoot)).toEqual(commonAncestorRoot); - logger.info("New block", { - slot: newBlock.block.message.slot, - parentRoot: toHexString(newBlock.block.message.parentRoot), - }); + ({slot}) => slot === reorgSlot + 1 + ), + waitForEvent(bn.chain.emitter, routes.events.EventType.chainReorg, 240000), + ]); + expect(reorgEventData.slot).toEqual(reorgSlot + 1); + const newBlock = await bn.chain.getBlockByRoot(newBlockEventData.block); + if (newBlock == null) { + throw Error(`Block ${reorgSlot + 1} not found`); + } + expect(reorgEventData.oldHeadBlock).toEqual(reorgBlockRoot); + expect(reorgEventData.newHeadBlock).toEqual(newBlockEventData.block); + expect(reorgEventData.depth).toEqual(2); + expect(toHexString(newBlock?.block.message.parentRoot)).toEqual(commonAncestorRoot); + logger.info("New block", { + slot: newBlock.block.message.slot, + parentRoot: toHexString(newBlock.block.message.parentRoot), }); - }, - {timeout: 60000} -); + }); +}); diff --git a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts index 54007423e821..005b28baeefc 100644 --- a/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts +++ b/packages/beacon-node/test/e2e/chain/stateCache/nHistoricalStates.test.ts @@ -1,4 +1,4 @@ -import {describe, it, afterEach, expect} from "vitest"; +import {describe, it, afterEach, expect, vi} from "vitest"; import {Gauge, Histogram} from "prom-client"; import {ChainConfig} from "@lodestar/config"; import {Slot, phase0} from "@lodestar/types"; @@ -19,429 +19,427 @@ import {ReorgedForkChoice} from "../../../mocks/fork-choice/reorg.js"; * This includes several tests which make >6 min to pass in CI, so let's only run 1 of them and leave remaining ones * for local investigation. */ -describe( - "regen/reload states with n-historical states configuration", - function () { - const validatorCount = 8; - const testParams: Pick = { - // eslint-disable-next-line @typescript-eslint/naming-convention - SECONDS_PER_SLOT: 2, - }; +describe("regen/reload states with n-historical states configuration", function () { + vi.setConfig({testTimeout: 96_000}); - const afterEachCallbacks: (() => Promise | void)[] = []; - afterEach(async () => { - while (afterEachCallbacks.length > 0) { - const callback = afterEachCallbacks.pop(); - if (callback) await callback(); - } - }); + const validatorCount = 8; + const testParams: Pick = { + // eslint-disable-next-line @typescript-eslint/naming-convention + SECONDS_PER_SLOT: 2, + }; + + const afterEachCallbacks: (() => Promise | void)[] = []; + afterEach(async () => { + while (afterEachCallbacks.length > 0) { + const callback = afterEachCallbacks.pop(); + if (callback) await callback(); + } + }); - // all tests run until this slot - const LAST_SLOT = 33; + // all tests run until this slot + const LAST_SLOT = 33; + /** + * (n+1) + * -----------------| + * / + * |---------|---------| + * ^ ^ + * (n+1-x) reorgedSlot n + * ^ + * commonAncestor + * |<--reorgDistance-->| + */ + const testCases: { + name: string; + reorgedSlot: number; + reorgDistance: number; + maxBlockStates: number; + maxCPStateEpochsInMemory: number; + reloadCount: number; + // total persist count, to compare to metrics + persistCount: number; + numStatesInMemory: number; + // number of states persisted at the end of test + numStatesPersisted: number; + numEpochsInMemory: number; + numEpochsPersisted: number; + skip?: boolean; + }[] = [ /** - * (n+1) - * -----------------| - * / - * |---------|---------| - * ^ ^ - * (n+1-x) reorgedSlot n - * ^ - * commonAncestor - * |<--reorgDistance-->| + * Block slot 28 has parent slot 25, block slot 26 and 27 are reorged + * --------------------|--- + * / ^ ^ ^ ^ + * / 28 29 32 33 + * |----------------|---------- + * ^ ^ ^ ^ + * 24 25 26 27 + * */ + { + name: "0 historical state, reorg in same epoch", + reorgedSlot: 27, + reorgDistance: 3, + maxBlockStates: 1, + maxCPStateEpochsInMemory: 0, + // reload at cp epoch 1 once to regen state 9 (12 - 3) + reloadCount: 1, + // persist for epoch 0 to 4, no need to persist cp epoch 3 again + persistCount: 5, + // run through slot 33, no state in memory + numStatesInMemory: 0, + // epoch 0 1 2 3 4 but finalized at epoch 2 so store checkpoint states for epoch 2 3 4 + numStatesPersisted: 3, + numEpochsInMemory: 0, + // epoch 0 1 2 3 4 but finalized at eopch 2 so store checkpoint states for epoch 2 3 4 + numEpochsPersisted: 3, + // chain is finalized at epoch 2 end of test + skip: true, + }, + /** + * Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged + * --------------------------|--- + * / | ^ ^ ^ ^ + * / | 28 29 32 33 + * |----------------|---------- + * 16 ^ ^ ^ ^ ^ + * ^ 23 24 25 26 27 + * reload ^ + * 2 checkpoint states at epoch 3 are persisted */ - const testCases: { - name: string; - reorgedSlot: number; - reorgDistance: number; - maxBlockStates: number; - maxCPStateEpochsInMemory: number; - reloadCount: number; - // total persist count, to compare to metrics - persistCount: number; - numStatesInMemory: number; - // number of states persisted at the end of test - numStatesPersisted: number; - numEpochsInMemory: number; - numEpochsPersisted: number; - skip?: boolean; - }[] = [ - /** - * Block slot 28 has parent slot 25, block slot 26 and 27 are reorged - * --------------------|--- - * / ^ ^ ^ ^ - * / 28 29 32 33 - * |----------------|---------- - * ^ ^ ^ ^ - * 24 25 26 27 - * */ - { - name: "0 historical state, reorg in same epoch", - reorgedSlot: 27, - reorgDistance: 3, - maxBlockStates: 1, - maxCPStateEpochsInMemory: 0, - // reload at cp epoch 1 once to regen state 9 (12 - 3) - reloadCount: 1, - // persist for epoch 0 to 4, no need to persist cp epoch 3 again - persistCount: 5, - // run through slot 33, no state in memory - numStatesInMemory: 0, - // epoch 0 1 2 3 4 but finalized at epoch 2 so store checkpoint states for epoch 2 3 4 - numStatesPersisted: 3, - numEpochsInMemory: 0, - // epoch 0 1 2 3 4 but finalized at eopch 2 so store checkpoint states for epoch 2 3 4 - numEpochsPersisted: 3, - // chain is finalized at epoch 2 end of test - skip: true, - }, - /** - * Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged - * --------------------------|--- - * / | ^ ^ ^ ^ - * / | 28 29 32 33 - * |----------------|---------- - * 16 ^ ^ ^ ^ ^ - * ^ 23 24 25 26 27 - * reload ^ - * 2 checkpoint states at epoch 3 are persisted - */ - { - name: "0 historical state, reorg 1 epoch", - reorgedSlot: 27, - reorgDistance: 5, - maxBlockStates: 1, - maxCPStateEpochsInMemory: 0, - // reload at cp epoch 2 once to regen state 23 (28 - 5) - reloadCount: 1, - // 1 cp state for epoch 0 1 2 4, and 2 cp states for epoch 3 (different roots) - persistCount: 6, - numStatesInMemory: 0, - // epoch 0 1 2 4 has 1 cp state, epoch 3 has 2 checkpoint states - numStatesPersisted: 6, - numEpochsInMemory: 0, - // epoch 0 1 2 3 4 - numEpochsPersisted: 5, - // chain is not finalized end of test - skip: true, - }, - /** - * Block slot 28 has parent slot 25, block slot 26 and 27 are reorged - * --------------------|--- - * / ^ ^ ^ ^ - * / 28 29 32 33 - * |----------------|---------- - * ^ ^ ^ ^ - * 24 25 26 27 - * */ - { - name: "maxCPStateEpochsInMemory=1, reorg in same epoch", - reorgedSlot: 27, - reorgDistance: 3, - maxBlockStates: 1, - maxCPStateEpochsInMemory: 1, - // no need to reload as cp state epoch 3 is available in memory - reloadCount: 0, - // 1 time for epoch 0 1 2 3, cp state epoch 4 is in memory - persistCount: 4, - // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State - numStatesInMemory: 2, - // epoch 2 3, epoch 4 is in-memory - numStatesPersisted: 2, - // epoch 3 - numEpochsInMemory: 1, - // epoch 2 3, epoch 4 is in-memory - numEpochsPersisted: 2, - // chain is finalized at epoch 2 end of test - skip: true, - }, - /** - * Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged - * --------------------------|--- - * / | ^ ^ ^ ^ - * / | 28 29 32 33 - * |----------------|---------- - * 16 ^ ^ ^ ^ ^ - * 23 24 25 26 27 - * ^ - * both PRCS and CRCS are persisted - */ - { - name: "maxCPStateEpochsInMemory=1, reorg last slot of previous epoch", - reorgedSlot: 27, - reorgDistance: 5, - maxBlockStates: 1, - maxCPStateEpochsInMemory: 1, - // PRCS at epoch 3 is available in memory so no need to reload - reloadCount: 0, - // {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} - persistCount: 5, - // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State - numStatesInMemory: 2, - // chain is not finalized, same to persistCount - numStatesPersisted: 5, - // epoch 4 - numEpochsInMemory: 1, - // chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted - numEpochsPersisted: 4, - // chain is NOT finalized end of test - skip: true, - }, - /** - * Block slot 28 has parent slot 19, block slot 24 25 26 and 27 are reorged - * --------------------------------|--- - * / | ^ ^ ^ ^ - * / | 28 29 32 33 - * |----------------|---------- - * 16 ^ ^ ^ ^ ^ ^ - * 19 23 24 25 26 27 - * ^ - * both PRCS and CRCS are persisted since their roots are unknown to block state 33 - */ - { - name: "maxCPStateEpochsInMemory=1, reorg middle slot of previous epoch", - reorgedSlot: 27, - reorgDistance: 9, - maxBlockStates: 1, - maxCPStateEpochsInMemory: 1, - // reload CP state epoch 2 (slot = 16) - reloadCount: 1, - // {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root19, epoch: 3} - persistCount: 6, - // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State - numStatesInMemory: 2, - // chain is not finalized, same to persist count - numStatesPersisted: 6, - // epoch 4 - numEpochsInMemory: 1, - // chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted - numEpochsPersisted: 4, - // chain is NOT finalized end of test - skip: true, - }, - /** - * Block slot 28 has parent slot 15, block slot 24 25 26 and 27 are reorged - * --------------------------------------------|--- - * / | ^ ^ ^ ^ - * / | 28 29 32 33 - * |----------------|----------------|---------- ^ - * ^ ^ 16 ^ ^ ^ ^ ^ ^ test end - * 8 15 19 23 24 25 26 27 - *reload ^ - * both PRCS and CRCS are persisted because roots are unknown to block 28 - */ - { - name: "maxCPStateEpochsInMemory=1, reorg 2 epochs", - reorgedSlot: 27, - reorgDistance: 13, - maxBlockStates: 1, - maxCPStateEpochsInMemory: 1, - // reload CP state epoch 2 (slot = 16) - reloadCount: 1, - // {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root15, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root15, epoch: 3} - persistCount: 7, - // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State - numStatesInMemory: 2, - // chain is not finalized, so same number to persistCount - numStatesPersisted: 7, - // epoch 4 - numEpochsInMemory: 1, - // chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted - numEpochsPersisted: 4, - // chain is NOT finalized end of test - }, - ]; + { + name: "0 historical state, reorg 1 epoch", + reorgedSlot: 27, + reorgDistance: 5, + maxBlockStates: 1, + maxCPStateEpochsInMemory: 0, + // reload at cp epoch 2 once to regen state 23 (28 - 5) + reloadCount: 1, + // 1 cp state for epoch 0 1 2 4, and 2 cp states for epoch 3 (different roots) + persistCount: 6, + numStatesInMemory: 0, + // epoch 0 1 2 4 has 1 cp state, epoch 3 has 2 checkpoint states + numStatesPersisted: 6, + numEpochsInMemory: 0, + // epoch 0 1 2 3 4 + numEpochsPersisted: 5, + // chain is not finalized end of test + skip: true, + }, + /** + * Block slot 28 has parent slot 25, block slot 26 and 27 are reorged + * --------------------|--- + * / ^ ^ ^ ^ + * / 28 29 32 33 + * |----------------|---------- + * ^ ^ ^ ^ + * 24 25 26 27 + * */ + { + name: "maxCPStateEpochsInMemory=1, reorg in same epoch", + reorgedSlot: 27, + reorgDistance: 3, + maxBlockStates: 1, + maxCPStateEpochsInMemory: 1, + // no need to reload as cp state epoch 3 is available in memory + reloadCount: 0, + // 1 time for epoch 0 1 2 3, cp state epoch 4 is in memory + persistCount: 4, + // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State + numStatesInMemory: 2, + // epoch 2 3, epoch 4 is in-memory + numStatesPersisted: 2, + // epoch 3 + numEpochsInMemory: 1, + // epoch 2 3, epoch 4 is in-memory + numEpochsPersisted: 2, + // chain is finalized at epoch 2 end of test + skip: true, + }, + /** + * Block slot 28 has parent slot 23, block slot 24 25 26 and 27 are reorged + * --------------------------|--- + * / | ^ ^ ^ ^ + * / | 28 29 32 33 + * |----------------|---------- + * 16 ^ ^ ^ ^ ^ + * 23 24 25 26 27 + * ^ + * both PRCS and CRCS are persisted + */ + { + name: "maxCPStateEpochsInMemory=1, reorg last slot of previous epoch", + reorgedSlot: 27, + reorgDistance: 5, + maxBlockStates: 1, + maxCPStateEpochsInMemory: 1, + // PRCS at epoch 3 is available in memory so no need to reload + reloadCount: 0, + // {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} + persistCount: 5, + // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State + numStatesInMemory: 2, + // chain is not finalized, same to persistCount + numStatesPersisted: 5, + // epoch 4 + numEpochsInMemory: 1, + // chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted + numEpochsPersisted: 4, + // chain is NOT finalized end of test + skip: true, + }, + /** + * Block slot 28 has parent slot 19, block slot 24 25 26 and 27 are reorged + * --------------------------------|--- + * / | ^ ^ ^ ^ + * / | 28 29 32 33 + * |----------------|---------- + * 16 ^ ^ ^ ^ ^ ^ + * 19 23 24 25 26 27 + * ^ + * both PRCS and CRCS are persisted since their roots are unknown to block state 33 + */ + { + name: "maxCPStateEpochsInMemory=1, reorg middle slot of previous epoch", + reorgedSlot: 27, + reorgDistance: 9, + maxBlockStates: 1, + maxCPStateEpochsInMemory: 1, + // reload CP state epoch 2 (slot = 16) + reloadCount: 1, + // {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root19, epoch: 3} + persistCount: 6, + // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State + numStatesInMemory: 2, + // chain is not finalized, same to persist count + numStatesPersisted: 6, + // epoch 4 + numEpochsInMemory: 1, + // chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted + numEpochsPersisted: 4, + // chain is NOT finalized end of test + skip: true, + }, + /** + * Block slot 28 has parent slot 15, block slot 24 25 26 and 27 are reorged + * --------------------------------------------|--- + * / | ^ ^ ^ ^ + * / | 28 29 32 33 + * |----------------|----------------|---------- ^ + * ^ ^ 16 ^ ^ ^ ^ ^ ^ test end + * 8 15 19 23 24 25 26 27 + *reload ^ + * both PRCS and CRCS are persisted because roots are unknown to block 28 + */ + { + name: "maxCPStateEpochsInMemory=1, reorg 2 epochs", + reorgedSlot: 27, + reorgDistance: 13, + maxBlockStates: 1, + maxCPStateEpochsInMemory: 1, + // reload CP state epoch 2 (slot = 16) + reloadCount: 1, + // {root0, epoch: 0} {root8, epoch: 1} {root16, epoch: 2} {root15, epoch: 2} {root23, epoch: 3} {root24, epoch: 3} {root15, epoch: 3} + persistCount: 7, + // epoch 4, one for Current Root Checkpoint State and one for Previous Root Checkpoint State + numStatesInMemory: 2, + // chain is not finalized, so same number to persistCount + numStatesPersisted: 7, + // epoch 4 + numEpochsInMemory: 1, + // chain is not finalized, epoch 4 is in-memory so CP state at epoch 0 1 2 3 are persisted + numEpochsPersisted: 4, + // chain is NOT finalized end of test + }, + ]; - for (const { - name, - reorgedSlot, - reorgDistance, - maxBlockStates, - maxCPStateEpochsInMemory, - reloadCount, - persistCount, - numStatesInMemory, - numStatesPersisted, - numEpochsInMemory, - numEpochsPersisted, - skip, - } of testCases) { - const wrappedIt = skip ? it.skip : it; - wrappedIt(`${name} reorgedSlot=${reorgedSlot} reorgDistance=${reorgDistance}`, async function () { - // the node needs time to transpile/initialize bls worker threads - const genesisSlotsDelay = 7; - const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; - const testLoggerOpts: TestLoggerOpts = { - level: LogLevel.debug, - timestampFormat: { - format: TimestampFormatCode.EpochSlot, - genesisTime, - slotsPerEpoch: SLOTS_PER_EPOCH, - secondsPerSlot: testParams.SECONDS_PER_SLOT, - }, - }; + for (const { + name, + reorgedSlot, + reorgDistance, + maxBlockStates, + maxCPStateEpochsInMemory, + reloadCount, + persistCount, + numStatesInMemory, + numStatesPersisted, + numEpochsInMemory, + numEpochsPersisted, + skip, + } of testCases) { + const wrappedIt = skip ? it.skip : it; + wrappedIt(`${name} reorgedSlot=${reorgedSlot} reorgDistance=${reorgDistance}`, async function () { + // the node needs time to transpile/initialize bls worker threads + const genesisSlotsDelay = 7; + const genesisTime = Math.floor(Date.now() / 1000) + genesisSlotsDelay * testParams.SECONDS_PER_SLOT; + const testLoggerOpts: TestLoggerOpts = { + level: LogLevel.debug, + timestampFormat: { + format: TimestampFormatCode.EpochSlot, + genesisTime, + slotsPerEpoch: SLOTS_PER_EPOCH, + secondsPerSlot: testParams.SECONDS_PER_SLOT, + }, + }; - const loggerNodeA = testLogger("Reorg-Node-A", testLoggerOpts); - const loggerNodeB = testLogger("FollowUp-Node-B", {...testLoggerOpts, level: LogLevel.debug}); + const loggerNodeA = testLogger("Reorg-Node-A", testLoggerOpts); + const loggerNodeB = testLogger("FollowUp-Node-B", {...testLoggerOpts, level: LogLevel.debug}); - const reorgedBn = await getDevBeaconNode({ - params: testParams, - options: { - sync: {isSingleNode: true}, - network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false}, - // run the first bn with ReorgedForkChoice, no nHistoricalStates flag so it does not have to reload - chain: { - blsVerifyAllMainThread: true, - forkchoiceConstructor: ReorgedForkChoice, - // this node does not need to reload state - nHistoricalStates: false, - proposerBoost: true, - }, + const reorgedBn = await getDevBeaconNode({ + params: testParams, + options: { + sync: {isSingleNode: true}, + network: {allowPublishToZeroPeers: true, mdns: true, useWorker: false}, + // run the first bn with ReorgedForkChoice, no nHistoricalStates flag so it does not have to reload + chain: { + blsVerifyAllMainThread: true, + forkchoiceConstructor: ReorgedForkChoice, + // this node does not need to reload state + nHistoricalStates: false, + proposerBoost: true, }, - validatorCount, - genesisTime, - logger: loggerNodeA, - }); + }, + validatorCount, + genesisTime, + logger: loggerNodeA, + }); - // stop bn after validators - afterEachCallbacks.push(() => reorgedBn.close()); + // stop bn after validators + afterEachCallbacks.push(() => reorgedBn.close()); - const followupBn = await getDevBeaconNode({ - params: testParams, - options: { - api: {rest: {enabled: false}}, - network: {mdns: true, useWorker: false}, - // run the 2nd bn with nHistoricalStates flag and the configured maxBlockStates, maxCPStateEpochsInMemory - chain: { - blsVerifyAllMainThread: true, - forkchoiceConstructor: ReorgedForkChoice, - // this node can follow with nHistoricalStates flag and it has to reload state - nHistoricalStates: true, - maxBlockStates, - maxCPStateEpochsInMemory, - proposerBoost: true, - }, - metrics: {enabled: true}, + const followupBn = await getDevBeaconNode({ + params: testParams, + options: { + api: {rest: {enabled: false}}, + network: {mdns: true, useWorker: false}, + // run the 2nd bn with nHistoricalStates flag and the configured maxBlockStates, maxCPStateEpochsInMemory + chain: { + blsVerifyAllMainThread: true, + forkchoiceConstructor: ReorgedForkChoice, + // this node can follow with nHistoricalStates flag and it has to reload state + nHistoricalStates: true, + maxBlockStates, + maxCPStateEpochsInMemory, + proposerBoost: true, }, - validatorCount, - genesisTime, - logger: loggerNodeB, - }); + metrics: {enabled: true}, + }, + validatorCount, + genesisTime, + logger: loggerNodeB, + }); - afterEachCallbacks.push(() => followupBn.close()); + afterEachCallbacks.push(() => followupBn.close()); - const connected = Promise.all([onPeerConnect(followupBn.network), onPeerConnect(reorgedBn.network)]); - await connect(followupBn.network, reorgedBn.network); - await connected; - loggerNodeB.info("Node B connected to Node A"); + const connected = Promise.all([onPeerConnect(followupBn.network), onPeerConnect(reorgedBn.network)]); + await connect(followupBn.network, reorgedBn.network); + await connected; + loggerNodeB.info("Node B connected to Node A"); - const {validators} = await getAndInitDevValidators({ - node: reorgedBn, - logPrefix: "Val-Node-A", - validatorsPerClient: validatorCount, - validatorClientCount: 1, - startIndex: 0, - useRestApi: false, - testLoggerOpts, - }); + const {validators} = await getAndInitDevValidators({ + node: reorgedBn, + logPrefix: "Val-Node-A", + validatorsPerClient: validatorCount, + validatorClientCount: 1, + startIndex: 0, + useRestApi: false, + testLoggerOpts, + }); - afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close()))); + afterEachCallbacks.push(() => Promise.all(validators.map((v) => v.close()))); - // wait for checkpoint 3 at slot 24, both nodes should reach same checkpoint - const cpEpoch = 3; - const cpSlot = 3 * SLOTS_PER_EPOCH; - const checkpoints = await Promise.all( - [reorgedBn, followupBn].map((bn) => - waitForEvent( - bn.chain.emitter, - ChainEvent.checkpoint, - (cpSlot + genesisSlotsDelay + 1) * testParams.SECONDS_PER_SLOT * 1000, - (cp) => cp.epoch === cpEpoch - ) + // wait for checkpoint 3 at slot 24, both nodes should reach same checkpoint + const cpEpoch = 3; + const cpSlot = 3 * SLOTS_PER_EPOCH; + const checkpoints = await Promise.all( + [reorgedBn, followupBn].map((bn) => + waitForEvent( + bn.chain.emitter, + ChainEvent.checkpoint, + (cpSlot + genesisSlotsDelay + 1) * testParams.SECONDS_PER_SLOT * 1000, + (cp) => cp.epoch === cpEpoch ) - ); - expect(checkpoints[0]).toEqual(checkpoints[1]); - expect(checkpoints[0].epoch).toEqual(3); - const head = reorgedBn.chain.forkChoice.getHead(); - loggerNodeA.info("Node A emitted checkpoint event, head slot: " + head.slot); + ) + ); + expect(checkpoints[0]).toEqual(checkpoints[1]); + expect(checkpoints[0].epoch).toEqual(3); + const head = reorgedBn.chain.forkChoice.getHead(); + loggerNodeA.info("Node A emitted checkpoint event, head slot: " + head.slot); - // setup reorg data for both bns - for (const bn of [reorgedBn, followupBn]) { - (bn.chain.forkChoice as ReorgedForkChoice).reorgedSlot = reorgedSlot; - (bn.chain.forkChoice as ReorgedForkChoice).reorgDistance = reorgDistance; - } + // setup reorg data for both bns + for (const bn of [reorgedBn, followupBn]) { + (bn.chain.forkChoice as ReorgedForkChoice).reorgedSlot = reorgedSlot; + (bn.chain.forkChoice as ReorgedForkChoice).reorgDistance = reorgDistance; + } - // both nodes see the reorg event - const reorgDatas = await Promise.all( - [reorgedBn, followupBn].map((bn) => - waitForEvent( - bn.chain.emitter, - routes.events.EventType.chainReorg, - // reorged event happens at reorgedSlot + 1 - (reorgedSlot + 1 - cpSlot + 1) * testParams.SECONDS_PER_SLOT * 1000, - (reorgData) => reorgData.slot === reorgedSlot + 1 - ) + // both nodes see the reorg event + const reorgDatas = await Promise.all( + [reorgedBn, followupBn].map((bn) => + waitForEvent( + bn.chain.emitter, + routes.events.EventType.chainReorg, + // reorged event happens at reorgedSlot + 1 + (reorgedSlot + 1 - cpSlot + 1) * testParams.SECONDS_PER_SLOT * 1000, + (reorgData) => reorgData.slot === reorgedSlot + 1 ) - ); - for (const reorgData of reorgDatas) { - expect(reorgData.slot).toEqual(reorgedSlot + 1); - expect(reorgData.depth).toEqual(reorgDistance); - } + ) + ); + for (const reorgData of reorgDatas) { + expect(reorgData.slot).toEqual(reorgedSlot + 1); + expect(reorgData.depth).toEqual(reorgDistance); + } - // make sure both nodes can reach another checkpoint - const checkpoints2 = await Promise.all( - [reorgedBn, followupBn].map((bn) => - waitForEvent(bn.chain.emitter, ChainEvent.checkpoint, 240000, (cp) => cp.epoch === 4) - ) - ); - expect(checkpoints2[0]).toEqual(checkpoints2[1]); - expect(checkpoints2[0].epoch).toEqual(4); + // make sure both nodes can reach another checkpoint + const checkpoints2 = await Promise.all( + [reorgedBn, followupBn].map((bn) => + waitForEvent(bn.chain.emitter, ChainEvent.checkpoint, 240000, (cp) => cp.epoch === 4) + ) + ); + expect(checkpoints2[0]).toEqual(checkpoints2[1]); + expect(checkpoints2[0].epoch).toEqual(4); - // wait for 1 more slot to persist states - await waitForEvent<{slot: Slot}>( - reorgedBn.chain.emitter, - routes.events.EventType.block, - 240000, - ({slot}) => slot === LAST_SLOT - ); + // wait for 1 more slot to persist states + await waitForEvent<{slot: Slot}>( + reorgedBn.chain.emitter, + routes.events.EventType.block, + 240000, + ({slot}) => slot === LAST_SLOT + ); - const reloadMetricValues = await (followupBn.metrics?.cpStateCache.stateReloadDuration as Histogram).get(); - expect( - reloadMetricValues?.values.find( - (value) => value.metricName === "lodestar_cp_state_cache_state_reload_seconds_count" - )?.value - ).toEqual(reloadCount); + const reloadMetricValues = await (followupBn.metrics?.cpStateCache.stateReloadDuration as Histogram).get(); + expect( + reloadMetricValues?.values.find( + (value) => value.metricName === "lodestar_cp_state_cache_state_reload_seconds_count" + )?.value + ).toEqual(reloadCount); - const stateSszMetricValues = await (followupBn.metrics?.cpStateCache.stateSerializeDuration as Histogram).get(); - expect( - stateSszMetricValues?.values.find( - (value) => value.metricName === "lodestar_cp_state_cache_state_serialize_seconds_count" - )?.value - ).toEqual(persistCount); + const stateSszMetricValues = await (followupBn.metrics?.cpStateCache.stateSerializeDuration as Histogram).get(); + expect( + stateSszMetricValues?.values.find( + (value) => value.metricName === "lodestar_cp_state_cache_state_serialize_seconds_count" + )?.value + ).toEqual(persistCount); - // assert number of persisted/in-memory states - const stateSizeMetricValues = await (followupBn.metrics?.cpStateCache.size as unknown as Gauge).get(); - const numStateInMemoryItem = stateSizeMetricValues?.values.find( - (value) => value.labels.type === CacheItemType.inMemory - ); - const numStatePersistedItem = stateSizeMetricValues?.values.find( - (value) => value.labels.type === CacheItemType.persisted - ); - expect(numStateInMemoryItem?.value).toEqual(numStatesInMemory); - expect(numStatePersistedItem?.value).toEqual(numStatesPersisted); + // assert number of persisted/in-memory states + const stateSizeMetricValues = await (followupBn.metrics?.cpStateCache.size as unknown as Gauge).get(); + const numStateInMemoryItem = stateSizeMetricValues?.values.find( + (value) => value.labels.type === CacheItemType.inMemory + ); + const numStatePersistedItem = stateSizeMetricValues?.values.find( + (value) => value.labels.type === CacheItemType.persisted + ); + expect(numStateInMemoryItem?.value).toEqual(numStatesInMemory); + expect(numStatePersistedItem?.value).toEqual(numStatesPersisted); - // assert number of epochs persisted/in-memory - const epochSizeMetricValues = await (followupBn.metrics?.cpStateCache.epochSize as unknown as Gauge).get(); - const numEpochsInMemoryItem = epochSizeMetricValues?.values.find( - (value) => value.labels.type === CacheItemType.inMemory - ); - const numEpochsPersistedItem = epochSizeMetricValues?.values.find( - (value) => value.labels.type === CacheItemType.persisted - ); - expect(numEpochsInMemoryItem?.value).toEqual(numEpochsInMemory); - expect(numEpochsPersistedItem?.value).toEqual(numEpochsPersisted); - }); - } - }, - {timeout: 96_000} -); + // assert number of epochs persisted/in-memory + const epochSizeMetricValues = await (followupBn.metrics?.cpStateCache.epochSize as unknown as Gauge).get(); + const numEpochsInMemoryItem = epochSizeMetricValues?.values.find( + (value) => value.labels.type === CacheItemType.inMemory + ); + const numEpochsPersistedItem = epochSizeMetricValues?.values.find( + (value) => value.labels.type === CacheItemType.persisted + ); + expect(numEpochsInMemoryItem?.value).toEqual(numEpochsInMemory); + expect(numEpochsPersistedItem?.value).toEqual(numEpochsPersisted); + }); + } +}); diff --git a/packages/beacon-node/test/e2e/network/gossipsub.test.ts b/packages/beacon-node/test/e2e/network/gossipsub.test.ts index fd1794bf549d..c19f3c7c4388 100644 --- a/packages/beacon-node/test/e2e/network/gossipsub.test.ts +++ b/packages/beacon-node/test/e2e/network/gossipsub.test.ts @@ -1,4 +1,4 @@ -import {describe, it, expect, afterEach} from "vitest"; +import {describe, it, expect, afterEach, vi} from "vitest"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {sleep} from "@lodestar/utils"; import {computeStartSlotAtEpoch} from "@lodestar/state-transition"; @@ -8,26 +8,22 @@ import {GossipType, GossipHandlers, GossipHandlerParamGeneric} from "../../../sr import {getNetworkForTest} from "../../utils/networkWithMockDb.js"; import {connect, onPeerConnect} from "../../utils/network.js"; -describe( - "gossipsub / main thread", - function () { - runTests({useWorker: false}); - }, - {timeout: 3000} -); +describe("gossipsub / main thread", function () { + vi.setConfig({testTimeout: 3000}); + + runTests({useWorker: false}); +}); /** * This is nice to have to investigate networking issue in local environment. * Since we use vitest to run tests in parallel, including this causes the test to be unstable. * See https://github.com/ChainSafe/lodestar/issues/6358 */ -describe.skip( - "gossipsub / worker", - function () { - runTests({useWorker: true}); - }, - {timeout: 10_000} -); +describe.skip("gossipsub / worker", function () { + vi.setConfig({testTimeout: 3000}); + + runTests({useWorker: true}); +}); function runTests({useWorker}: {useWorker: boolean}): void { const afterEachCallbacks: (() => Promise | void)[] = []; diff --git a/packages/beacon-node/test/e2e/network/network.test.ts b/packages/beacon-node/test/e2e/network/network.test.ts index 40bf6b7e14af..094a75280e8b 100644 --- a/packages/beacon-node/test/e2e/network/network.test.ts +++ b/packages/beacon-node/test/e2e/network/network.test.ts @@ -9,21 +9,17 @@ import {connect, disconnect, onPeerConnect, onPeerDisconnect} from "../../utils/ import {getNetworkForTest} from "../../utils/networkWithMockDb.js"; import {getValidPeerId} from "../../utils/peer.js"; -describe( - "network / main thread", - function () { - runTests({useWorker: false}); - }, - {timeout: 3000} -); - -describe( - "network / worker", - function () { - runTests({useWorker: true}); - }, - {timeout: 10_000} -); +describe("network / main thread", function () { + vi.setConfig({testTimeout: 3000}); + + runTests({useWorker: false}); +}); + +describe("network / worker", function () { + vi.setConfig({testTimeout: 10_000}); + + runTests({useWorker: true}); +}); function runTests({useWorker}: {useWorker: boolean}): void { const afterEachCallbacks: (() => Promise | void)[] = []; @@ -111,11 +107,11 @@ function runTests({useWorker}: {useWorker: boolean}): void { // NetworkEvent.reqRespRequest does not work on worker thread // so we only test the peerDisconnected event - const onGoodbyeNetB = useWorker ? null : vi.fn<[phase0.Goodbye, PeerId]>(); + const onGoodbyeNetB = useWorker ? null : vi.fn<(message: phase0.Goodbye, peerId: PeerId) => void>(); netB.events.on(NetworkEvent.reqRespRequest, ({request, peer}) => { if (request.method === ReqRespMethod.Goodbye && onGoodbyeNetB) onGoodbyeNetB(request.body, peer); }); - const onDisconnectNetB = vi.fn<[string]>(); + const onDisconnectNetB = vi.fn<(_: string) => void>(); netB.events.on(NetworkEvent.peerDisconnected, ({peer}) => { onDisconnectNetB(peer); }); diff --git a/packages/beacon-node/test/e2e/network/reqresp.test.ts b/packages/beacon-node/test/e2e/network/reqresp.test.ts index 7969282194dd..a3c8b7b66ca0 100644 --- a/packages/beacon-node/test/e2e/network/reqresp.test.ts +++ b/packages/beacon-node/test/e2e/network/reqresp.test.ts @@ -1,4 +1,4 @@ -import {describe, it, expect, afterEach, beforeEach} from "vitest"; +import {describe, it, expect, afterEach, beforeEach, vi} from "vitest"; import {createChainForkConfig, ChainForkConfig} from "@lodestar/config"; import {chainConfig} from "@lodestar/config/default"; import {ForkName} from "@lodestar/params"; @@ -15,21 +15,17 @@ import {PeerIdStr} from "../../../src/util/peerId.js"; /* eslint-disable require-yield, @typescript-eslint/naming-convention */ -describe( - "network / reqresp / main thread", - function () { - runTests({useWorker: false}); - }, - {timeout: 3000} -); - -describe( - "network / reqresp / worker", - function () { - runTests({useWorker: true}); - }, - {timeout: 30_000} -); +describe("network / reqresp / main thread", function () { + vi.setConfig({testTimeout: 3000}); + + runTests({useWorker: false}); +}); + +describe("network / reqresp / worker", function () { + vi.setConfig({testTimeout: 30_000}); + + runTests({useWorker: true}); +}); function runTests({useWorker}: {useWorker: boolean}): void { // Schedule ALTAIR_FORK_EPOCH to trigger registering lightclient ReqResp protocols immediately diff --git a/packages/beacon-node/test/mocks/mockedBeaconChain.ts b/packages/beacon-node/test/mocks/mockedBeaconChain.ts index b63eb11435ca..cc85cfd7d553 100644 --- a/packages/beacon-node/test/mocks/mockedBeaconChain.ts +++ b/packages/beacon-node/test/mocks/mockedBeaconChain.ts @@ -20,7 +20,7 @@ import {getMockedClock} from "./clock.js"; export type MockedBeaconChain = Mocked & { logger: Mocked; - getHeadState: Mock<[]>; + getHeadState: Mock; forkChoice: MockedForkChoice; executionEngine: Mocked; executionBuilder: Mocked; @@ -31,10 +31,10 @@ export type MockedBeaconChain = Mocked & { shufflingCache: Mocked; regen: Mocked; bls: { - verifySignatureSets: Mock<[boolean]>; - verifySignatureSetsSameMessage: Mock<[boolean]>; + verifySignatureSets: Mock<() => boolean>; + verifySignatureSetsSameMessage: Mock<() => boolean>; close: Mock; - canAcceptWork: Mock<[boolean]>; + canAcceptWork: Mock<() => boolean>; }; lightClientServer: Mocked; }; @@ -117,6 +117,7 @@ vi.mock("../../src/chain/chain.js", async (importActual) => { executionEngine: { notifyForkchoiceUpdate: vi.fn(), getPayload: vi.fn(), + getClientVersion: vi.fn(), }, executionBuilder: {}, // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/beacon-node/test/mocks/mockedBls.ts b/packages/beacon-node/test/mocks/mockedBls.ts index 0ecec5f13bde..bc6bbf361de5 100644 --- a/packages/beacon-node/test/mocks/mockedBls.ts +++ b/packages/beacon-node/test/mocks/mockedBls.ts @@ -1,4 +1,4 @@ -import {PublicKey} from "@chainsafe/bls/types"; +import {PublicKey} from "@chainsafe/blst"; import {IBlsVerifier} from "../../src/chain/bls/index.js"; export class BlsVerifierMock implements IBlsVerifier { diff --git a/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts b/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts index 10527fdf94b5..803d6c84c408 100644 --- a/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts +++ b/packages/beacon-node/test/perf/api/impl/validator/attester.test.ts @@ -1,5 +1,4 @@ import {itBench} from "@dapplion/benchmark"; -import {PointFormat} from "@chainsafe/bls/types"; // eslint-disable-next-line import/no-relative-packages import {generatePerfTestCachedStatePhase0, numValidators} from "../../../../../../state-transition/test/perf/util.js"; import {getPubkeysForIndices} from "../../../../../src/api/impl/validator/utils.js"; @@ -36,7 +35,7 @@ describe("api / impl / validator", () => { fn: () => { for (let i = 0; i < reqCount; i++) { const pubkey = state.epochCtx.index2pubkey[i]; - pubkey.toBytes(PointFormat.compressed); + pubkey.toBytes(); } }, }); diff --git a/packages/beacon-node/test/perf/bls/bls.test.ts b/packages/beacon-node/test/perf/bls/bls.test.ts index a982cc55e499..988052f4a95c 100644 --- a/packages/beacon-node/test/perf/bls/bls.test.ts +++ b/packages/beacon-node/test/perf/bls/bls.test.ts @@ -1,7 +1,14 @@ import crypto from "node:crypto"; import {itBench} from "@dapplion/benchmark"; -import bls from "@chainsafe/bls"; -import {CoordType, type PublicKey, type SecretKey} from "@chainsafe/bls/types"; +import { + PublicKey, + SecretKey, + Signature, + aggregatePublicKeys, + aggregateSignatures, + verify, + verifyMultipleAggregateSignatures, +} from "@chainsafe/blst"; import {linspace} from "../../../src/util/numpy.js"; describe("BLS ops", function () { @@ -20,7 +27,7 @@ describe("BLS ops", function () { const bytes = new Uint8Array(32); const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); dataView.setUint32(0, i + 1, true); - const secretKey = bls.SecretKey.fromBytes(bytes); + const secretKey = SecretKey.fromKeygen(bytes); const publicKey = secretKey.toPublicKey(); keypair = {secretKey, publicKey}; keypairs.set(i, keypair); @@ -53,8 +60,8 @@ describe("BLS ops", function () { } // Note: getSet() caches the value, does not re-compute every time - itBench({id: `BLS verify - ${bls.implementation}`, beforeEach: () => getSet(0)}, (set) => { - const isValid = bls.Signature.fromBytes(set.signature).verify(set.publicKey, set.message); + itBench({id: "BLS verify - blst", beforeEach: () => getSet(0)}, (set) => { + const isValid = verify(set.message, set.publicKey, Signature.fromBytes(set.signature)); if (!isValid) throw Error("Invalid"); }); @@ -62,14 +69,14 @@ describe("BLS ops", function () { // We may want to bundle up to 32 sets in a single batch. for (const count of [3, 8, 32, 64, 128]) { itBench({ - id: `BLS verifyMultipleSignatures ${count} - ${bls.implementation}`, + id: `BLS verifyMultipleSignatures ${count} - blst`, beforeEach: () => linspace(0, count - 1).map((i) => getSet(i)), fn: (sets) => { - const isValid = bls.Signature.verifyMultipleSignatures( + const isValid = verifyMultipleAggregateSignatures( sets.map((set) => ({ - publicKey: set.publicKey, - message: set.message, - signature: bls.Signature.fromBytes(set.signature), + pk: set.publicKey, + msg: set.message, + sig: Signature.fromBytes(set.signature), })) ); if (!isValid) throw Error("Invalid"); @@ -86,7 +93,7 @@ describe("BLS ops", function () { fn: () => { for (const signature of signatures) { // true = validate signature - bls.Signature.fromBytes(signature, CoordType.affine, true); + Signature.fromBytes(signature, true); } }, }); @@ -97,15 +104,13 @@ describe("BLS ops", function () { // TODO: figure out why it does not work with 256 or more for (const count of [3, 8, 32, 64, 128]) { itBench({ - id: `BLS verifyMultipleSignatures - same message - ${count} - ${bls.implementation}`, + id: `BLS verifyMultipleSignatures - same message - ${count} - blst`, beforeEach: () => linspace(0, count - 1).map((i) => getSetSameMessage(i)), fn: (sets) => { // aggregate and verify aggregated signatures - const aggregatedPubkey = bls.PublicKey.aggregate(sets.map((set) => set.publicKey)); - const aggregatedSignature = bls.Signature.aggregate( - sets.map((set) => bls.Signature.fromBytes(set.signature, CoordType.affine, false)) - ); - const isValid = aggregatedSignature.verify(aggregatedPubkey, sets[0].message); + const aggregatedPubkey = aggregatePublicKeys(sets.map((set) => set.publicKey)); + const aggregatedSignature = aggregateSignatures(sets.map((set) => Signature.fromBytes(set.signature))); + const isValid = verify(sets[0].message, aggregatedPubkey, aggregatedSignature); if (!isValid) throw Error("Invalid"); }, }); @@ -114,10 +119,10 @@ describe("BLS ops", function () { // Attestations in Mainnet contain 128 max on average for (const count of [32, 128]) { itBench({ - id: `BLS aggregatePubkeys ${count} - ${bls.implementation}`, + id: `BLS aggregatePubkeys ${count} - blst`, beforeEach: () => linspace(0, count - 1).map((i) => getKeypair(i).publicKey), fn: (pubkeys) => { - bls.PublicKey.aggregate(pubkeys); + aggregatePublicKeys(pubkeys); }, }); } diff --git a/packages/beacon-node/test/perf/util/dataview.test.ts b/packages/beacon-node/test/perf/util/dataview.test.ts new file mode 100644 index 000000000000..e0f28a5079e3 --- /dev/null +++ b/packages/beacon-node/test/perf/util/dataview.test.ts @@ -0,0 +1,29 @@ +import {itBench} from "@dapplion/benchmark"; + +describe("dataview", function () { + const data = Uint8Array.from([0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]); + + itBench({ + id: "getUint32 - dataview", + beforeEach: () => { + return 0; + }, + fn: (offset) => { + const view = new DataView(data.buffer, data.byteOffset, data.byteLength); + view.getUint32(offset, true); + }, + }); + + itBench({ + id: "getUint32 - manual", + beforeEach: () => { + return 0; + }, + fn: (offset) => { + // check high bytes for non-zero values + (data[offset + 4] | data[offset + 5] | data[offset + 6] | data[offset + 7]) === 0; + // create the uint32 + (data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24)) >>> 0; + }, + }); +}); diff --git a/packages/beacon-node/test/spec/bls/bls.ts b/packages/beacon-node/test/spec/bls/bls.ts index c8d42ad84c5f..5af15bcb8eb2 100644 --- a/packages/beacon-node/test/spec/bls/bls.ts +++ b/packages/beacon-node/test/spec/bls/bls.ts @@ -1,7 +1,14 @@ -import bls from "@chainsafe/bls"; -import {CoordType} from "@chainsafe/bls/types"; +import { + PublicKey, + SecretKey, + Signature, + aggregateSignatures, + aggregateVerify, + fastAggregateVerify, + verifyMultipleAggregateSignatures, + verify as _verify, +} from "@chainsafe/blst"; import {fromHexString} from "@chainsafe/ssz"; -import {toHexString} from "@lodestar/utils"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -31,7 +38,15 @@ export const testFnByType: Record any)> = { */ function aggregate_verify(input: {pubkeys: string[]; messages: string[]; signature: string}): boolean { const {pubkeys, messages, signature} = input; - return bls.verifyMultiple(pubkeys.map(fromHexString), messages.map(fromHexString), fromHexString(signature)); + try { + return aggregateVerify( + messages.map(fromHexString), + pubkeys.map((pk) => PublicKey.fromHex(pk)), + Signature.fromHex(signature) + ); + } catch (e) { + return false; + } } /** @@ -41,8 +56,8 @@ function aggregate_verify(input: {pubkeys: string[]; messages: string[]; signatu * ``` */ function aggregate(input: string[]): string { - const pks = input.map((pkHex) => bls.Signature.fromHex(pkHex)); - const agg = bls.Signature.aggregate(pks); + const pks = input.map((pkHex) => Signature.fromHex(pkHex)); + const agg = aggregateSignatures(pks); return agg.toHex(); } @@ -58,9 +73,10 @@ function aggregate(input: string[]): string { function fast_aggregate_verify(input: {pubkeys: string[]; message: string; signature: string}): boolean | null { const {pubkeys, message, signature} = input; try { - return bls.Signature.fromBytes(fromHexString(signature), undefined, true).verifyAggregate( - pubkeys.map((hex) => bls.PublicKey.fromBytes(fromHexString(hex), CoordType.jacobian, true)), - fromHexString(message) + return fastAggregateVerify( + fromHexString(message), + pubkeys.map((hex) => PublicKey.fromHex(hex, true)), + Signature.fromHex(signature, true) ); } catch (e) { return false; @@ -80,11 +96,11 @@ function fast_aggregate_verify(input: {pubkeys: string[]; message: string; signa function batch_verify(input: {pubkeys: string[]; messages: string[]; signatures: string[]}): boolean | null { const {pubkeys, messages, signatures} = input; try { - return bls.Signature.verifyMultipleSignatures( + return verifyMultipleAggregateSignatures( pubkeys.map((pubkey, i) => ({ - publicKey: bls.PublicKey.fromBytes(fromHexString(pubkey), CoordType.jacobian, true), - message: fromHexString(messages[i]), - signature: bls.Signature.fromBytes(fromHexString(signatures[i]), undefined, true), + pk: PublicKey.fromHex(pubkey, true), + msg: fromHexString(messages[i]), + sig: Signature.fromHex(signatures[i], true), })) ); } catch (e) { @@ -103,8 +119,8 @@ function batch_verify(input: {pubkeys: string[]; messages: string[]; signatures: */ function sign(input: {privkey: string; message: string}): string | null { const {privkey, message} = input; - const signature = bls.sign(fromHexString(privkey), fromHexString(message)); - return toHexString(signature); + const signature = SecretKey.fromHex(privkey).sign(fromHexString(message)); + return signature.toHex(); } /** @@ -119,7 +135,11 @@ function sign(input: {privkey: string; message: string}): string | null { */ function verify(input: {pubkey: string; message: string; signature: string}): boolean { const {pubkey, message, signature} = input; - return bls.verify(fromHexString(pubkey), fromHexString(message), fromHexString(signature)); + try { + return _verify(fromHexString(message), PublicKey.fromHex(pubkey), Signature.fromHex(signature)); + } catch (e) { + return false; + } } /** @@ -131,7 +151,7 @@ function verify(input: {pubkey: string; message: string; signature: string}): bo */ function deserialization_G1(input: {pubkey: string}): boolean { try { - bls.PublicKey.fromBytes(fromHexString(input.pubkey), CoordType.jacobian, true); + PublicKey.fromHex(input.pubkey, true); return true; } catch (e) { return false; @@ -147,7 +167,7 @@ function deserialization_G1(input: {pubkey: string}): boolean { */ function deserialization_G2(input: {signature: string}): boolean { try { - bls.Signature.fromBytes(fromHexString(input.signature), undefined, true); + Signature.fromHex(input.signature, true); return true; } catch (e) { return false; diff --git a/packages/beacon-node/test/spec/general/bls.ts b/packages/beacon-node/test/spec/general/bls.ts index 88c1d79bcb79..010a5b9be53d 100644 --- a/packages/beacon-node/test/spec/general/bls.ts +++ b/packages/beacon-node/test/spec/general/bls.ts @@ -1,8 +1,15 @@ -import bls from "@chainsafe/bls"; -import {CoordType} from "@chainsafe/bls/types"; import {fromHexString} from "@chainsafe/ssz"; +import { + PublicKey, + SecretKey, + Signature, + aggregateSerializedPublicKeys, + aggregateSignatures, + aggregateVerify, + fastAggregateVerify, + verify as _verify, +} from "@chainsafe/blst"; import {InputType} from "@lodestar/spec-test-util"; -import {toHexString} from "@lodestar/utils"; import {TestRunnerFn} from "../utils/types.js"; /* eslint-disable @typescript-eslint/naming-convention */ @@ -63,10 +70,14 @@ type BlsTestCase = { * output: BLS Signature -- expected output, single BLS signature or empty. * ``` */ -function aggregate(input: string[]): string { - const pks = input.map((pkHex) => bls.Signature.fromHex(pkHex)); - const agg = bls.Signature.aggregate(pks); - return agg.toHex(); +function aggregate(input: string[]): string | null { + try { + const pks = input.map((pkHex) => Signature.fromHex(pkHex)); + const agg = aggregateSignatures(pks); + return agg.toHex(); + } catch (e) { + return null; + } } /** @@ -80,7 +91,15 @@ function aggregate(input: string[]): string { */ function aggregate_verify(input: {pubkeys: string[]; messages: string[]; signature: string}): boolean { const {pubkeys, messages, signature} = input; - return bls.verifyMultiple(pubkeys.map(fromHexString), messages.map(fromHexString), fromHexString(signature)); + try { + return aggregateVerify( + messages.map(fromHexString), + pubkeys.map((pk) => PublicKey.fromHex(pk)), + Signature.fromHex(signature) + ); + } catch (e) { + return false; + } } /** @@ -95,8 +114,11 @@ function eth_aggregate_pubkeys(input: string[]): string | null { if (pk === G1_POINT_AT_INFINITY) return null; } - const agg = bls.aggregatePublicKeys(input.map((hex) => fromHexString(hex))); - return toHexString(agg); + try { + return aggregateSerializedPublicKeys(input.map((hex) => fromHexString(hex))).toHex(); + } catch (e) { + return null; + } } /** @@ -120,11 +142,15 @@ function eth_fast_aggregate_verify(input: {pubkeys: string[]; message: string; s if (pk === G1_POINT_AT_INFINITY) return false; } - return bls.verifyAggregate( - pubkeys.map((hex) => fromHexString(hex)), - fromHexString(message), - fromHexString(signature) - ); + try { + return fastAggregateVerify( + fromHexString(message), + pubkeys.map((hex) => PublicKey.fromHex(hex)), + Signature.fromHex(signature) + ); + } catch (e) { + return false; + } } /** @@ -139,9 +165,10 @@ function eth_fast_aggregate_verify(input: {pubkeys: string[]; message: string; s function fast_aggregate_verify(input: {pubkeys: string[]; message: string; signature: string}): boolean | null { const {pubkeys, message, signature} = input; try { - return bls.Signature.fromBytes(fromHexString(signature), undefined, true).verifyAggregate( - pubkeys.map((hex) => bls.PublicKey.fromBytes(fromHexString(hex), CoordType.jacobian, true)), - fromHexString(message) + return fastAggregateVerify( + fromHexString(message), + pubkeys.map((hex) => PublicKey.fromHex(hex, true)), + Signature.fromHex(signature, true) ); } catch (e) { return false; @@ -156,8 +183,11 @@ function fast_aggregate_verify(input: {pubkeys: string[]; message: string; signa */ function sign(input: {privkey: string; message: string}): string | null { const {privkey, message} = input; - const signature = bls.sign(fromHexString(privkey), fromHexString(message)); - return toHexString(signature); + try { + return SecretKey.fromHex(privkey).sign(fromHexString(message)).toHex(); + } catch (e) { + return null; + } } /** @@ -169,5 +199,9 @@ function sign(input: {privkey: string; message: string}): string | null { */ function verify(input: {pubkey: string; message: string; signature: string}): boolean { const {pubkey, message, signature} = input; - return bls.verify(fromHexString(pubkey), fromHexString(message), fromHexString(signature)); + try { + return _verify(fromHexString(message), PublicKey.fromHex(pubkey), Signature.fromHex(signature)); + } catch (e) { + return false; + } } diff --git a/packages/beacon-node/test/spec/presets/light_client/sync.ts b/packages/beacon-node/test/spec/presets/light_client/sync.ts index 4b264e5b65e4..bfd3d0d0bb3d 100644 --- a/packages/beacon-node/test/spec/presets/light_client/sync.ts +++ b/packages/beacon-node/test/spec/presets/light_client/sync.ts @@ -1,5 +1,4 @@ import {expect} from "vitest"; -import {init} from "@chainsafe/bls/switchable"; import {isForkLightClient} from "@lodestar/params"; import {altair, phase0, RootHex, Slot, ssz, sszTypesFor} from "@lodestar/types"; import {InputType} from "@lodestar/spec-test-util"; @@ -78,8 +77,6 @@ const UPDATE_FILE_NAME = "^(update)_([0-9a-zA-Z_]+)$"; export const sync: TestRunnerFn = (fork) => { return { testFunction: async (testcase) => { - await init("blst-native"); - // Grab only the ALTAIR_FORK_EPOCH, since the domains are the same as minimal const config = createBeaconConfig( pickConfigForkEpochs(testcase.config), diff --git a/packages/beacon-node/test/unit/api/impl/validator/duties/proposer.test.ts b/packages/beacon-node/test/unit/api/impl/validator/duties/proposer.test.ts index d4f7705fb25b..b101382e01a0 100644 --- a/packages/beacon-node/test/unit/api/impl/validator/duties/proposer.test.ts +++ b/packages/beacon-node/test/unit/api/impl/validator/duties/proposer.test.ts @@ -10,6 +10,7 @@ import {generateState, zeroProtoBlock} from "../../../../../utils/state.js"; import {generateValidators} from "../../../../../utils/validator.js"; import {createCachedBeaconStateTest} from "../../../../../utils/cachedBeaconState.js"; import {SyncState} from "../../../../../../src/sync/interface.js"; +import {defaultApiOptions} from "../../../../../../src/api/options.js"; describe("get proposers api impl", function () { let api: ReturnType; @@ -20,7 +21,7 @@ describe("get proposers api impl", function () { beforeEach(function () { vi.useFakeTimers({now: 0}); modules = getApiTestModules({clock: "real"}); - api = getValidatorApi(modules); + api = getValidatorApi(defaultApiOptions, modules); state = generateState( { diff --git a/packages/beacon-node/test/unit/api/impl/validator/produceAttestationData.test.ts b/packages/beacon-node/test/unit/api/impl/validator/produceAttestationData.test.ts index 256d772d5fc0..84872ca6045c 100644 --- a/packages/beacon-node/test/unit/api/impl/validator/produceAttestationData.test.ts +++ b/packages/beacon-node/test/unit/api/impl/validator/produceAttestationData.test.ts @@ -3,6 +3,7 @@ import {ProtoBlock} from "@lodestar/fork-choice"; import {SyncState} from "../../../../../src/sync/interface.js"; import {ApiTestModules, getApiTestModules} from "../../../../utils/api.js"; import {getValidatorApi} from "../../../../../src/api/impl/validator/index.js"; +import {defaultApiOptions} from "../../../../../src/api/options.js"; describe("api - validator - produceAttestationData", function () { let modules: ApiTestModules; @@ -10,7 +11,7 @@ describe("api - validator - produceAttestationData", function () { beforeEach(function () { modules = getApiTestModules(); - api = getValidatorApi(modules); + api = getValidatorApi(defaultApiOptions, modules); }); it("Should throw when node is not synced", async function () { diff --git a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts index 0868834e619e..a23373938f64 100644 --- a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts +++ b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts @@ -14,6 +14,7 @@ import {toGraffitiBuffer} from "../../../../../src/util/graffiti.js"; import {BlockType, produceBlockBody} from "../../../../../src/chain/produceBlock/produceBlockBody.js"; import {generateProtoBlock} from "../../../../utils/typeGenerator.js"; import {ZERO_HASH_HEX} from "../../../../../src/constants/index.js"; +import {defaultApiOptions} from "../../../../../src/api/options.js"; describe("api/validator - produceBlockV2", function () { let api: ReturnType; @@ -22,7 +23,7 @@ describe("api/validator - produceBlockV2", function () { beforeEach(() => { modules = getApiTestModules(); - api = getValidatorApi(modules); + api = getValidatorApi(defaultApiOptions, modules); state = generateCachedBellatrixState(); }); diff --git a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts index 851bc9d0f33b..a7299aa3b956 100644 --- a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts +++ b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV3.test.ts @@ -10,6 +10,7 @@ import {SyncState} from "../../../../../src/sync/interface.js"; import {getValidatorApi} from "../../../../../src/api/impl/validator/index.js"; import {CommonBlockBody} from "../../../../../src/chain/interface.js"; import {zeroProtoBlock} from "../../../../utils/state.js"; +import {defaultApiOptions} from "../../../../../src/api/options.js"; /* eslint-disable @typescript-eslint/naming-convention */ describe("api/validator - produceBlockV3", function () { @@ -26,7 +27,7 @@ describe("api/validator - produceBlockV3", function () { beforeEach(() => { modules = getApiTestModules(); - api = getValidatorApi({...modules, config}); + api = getValidatorApi(defaultApiOptions, {...modules, config}); modules.chain.executionBuilder.status = true; }); diff --git a/packages/beacon-node/test/unit/chain/bls/bls.test.ts b/packages/beacon-node/test/unit/chain/bls/bls.test.ts index e5b844262632..4203d7f9768c 100644 --- a/packages/beacon-node/test/unit/chain/bls/bls.test.ts +++ b/packages/beacon-node/test/unit/chain/bls/bls.test.ts @@ -1,7 +1,5 @@ -import bls from "@chainsafe/bls"; -import {CoordType} from "@chainsafe/blst"; -import {PublicKey} from "@chainsafe/bls/types"; import {describe, it, expect, beforeEach} from "vitest"; +import {PublicKey, SecretKey, Signature} from "@chainsafe/blst"; import {ISignatureSet, SignatureSetType} from "@lodestar/state-transition"; import {BlsSingleThreadVerifier} from "../../../../src/chain/bls/singleThread.js"; import {BlsMultiThreadWorkerPool} from "../../../../src/chain/bls/multithread/index.js"; @@ -10,7 +8,7 @@ import {testLogger} from "../../../utils/logger.js"; describe("BlsVerifier ", function () { // take time for creating thread pool const numKeys = 3; - const secretKeys = Array.from({length: numKeys}, (_, i) => bls.SecretKey.fromKeygen(Buffer.alloc(32, i))); + const secretKeys = Array.from({length: numKeys}, (_, i) => SecretKey.fromKeygen(Buffer.alloc(32, i))); const verifiers = [ new BlsSingleThreadVerifier({metrics: null}), new BlsMultiThreadWorkerPool({}, {metrics: null, logger: testLogger()}), @@ -46,7 +44,7 @@ describe("BlsVerifier ", function () { it("should return false if at least one signature is malformed", async () => { // signature is malformed const malformedSignature = Buffer.alloc(96, 10); - expect(() => bls.Signature.fromBytes(malformedSignature, CoordType.affine, true)).toThrow(); + expect(() => Signature.fromBytes(malformedSignature, true, true)).toThrow(); sets[1].signature = malformedSignature; expect(await verifier.verifySignatureSets(sets)).toBe(false); }); @@ -79,7 +77,7 @@ describe("BlsVerifier ", function () { it("should return false for malformed signature", async () => { // signature is malformed const malformedSignature = Buffer.alloc(96, 10); - expect(() => bls.Signature.fromBytes(malformedSignature, CoordType.affine, true)).toThrow(); + expect(() => Signature.fromBytes(malformedSignature, true, true)).toThrow(); sets[1].signature = malformedSignature; expect(await verifier.verifySignatureSetsSameMessage(sets, signingRoot)).toEqual([true, false, true]); }); diff --git a/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts b/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts index 986ca074242f..f836e3621cc9 100644 --- a/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts +++ b/packages/beacon-node/test/unit/chain/genesis/genesis.test.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import type {SecretKey, PublicKey} from "@chainsafe/bls/types"; import {toHexString} from "@chainsafe/ssz"; import {describe, it, expect} from "vitest"; +import {PublicKey, SecretKey} from "@chainsafe/blst"; import {DOMAIN_DEPOSIT, MAX_EFFECTIVE_BALANCE} from "@lodestar/params"; import {config} from "@lodestar/config/default"; import {computeDomain, computeSigningRoot, interopSecretKey, ZERO_HASH} from "@lodestar/state-transition"; diff --git a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts index 3c248ad4d194..800984fa84bc 100644 --- a/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/aggregatedAttestationPool.test.ts @@ -1,7 +1,6 @@ -import type {SecretKey} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; import {BitArray, fromHexString, toHexString} from "@chainsafe/ssz"; import {describe, it, expect, beforeEach, beforeAll, afterEach, vi} from "vitest"; +import {SecretKey, Signature, fastAggregateVerify} from "@chainsafe/blst"; import {CachedBeaconStateAllForks, newFilledArray} from "@lodestar/state-transition"; import {FAR_FUTURE_EPOCH, ForkName, MAX_EFFECTIVE_BALANCE, SLOTS_PER_EPOCH} from "@lodestar/params"; import {ssz, phase0} from "@lodestar/types"; @@ -303,8 +302,8 @@ describe("MatchingDataAttestationGroup aggregateInto", function () { let sk2: SecretKey; beforeAll(async () => { - sk1 = bls.SecretKey.fromBytes(Buffer.alloc(32, 1)); - sk2 = bls.SecretKey.fromBytes(Buffer.alloc(32, 2)); + sk1 = SecretKey.fromBytes(Buffer.alloc(32, 1)); + sk2 = SecretKey.fromBytes(Buffer.alloc(32, 2)); attestation1.signature = sk1.sign(attestationDataRoot).toBytes(); attestation2.signature = sk2.sign(attestationDataRoot).toBytes(); }); @@ -315,7 +314,9 @@ describe("MatchingDataAttestationGroup aggregateInto", function () { aggregateInto(attWithIndex1, attWithIndex2); expect(renderBitArray(attWithIndex1.attestation.aggregationBits)).toEqual(renderBitArray(mergedBitArray)); - const aggregatedSignature = bls.Signature.fromBytes(attWithIndex1.attestation.signature, undefined, true); - expect(aggregatedSignature.verifyAggregate([sk1.toPublicKey(), sk2.toPublicKey()], attestationDataRoot)).toBe(true); + const aggregatedSignature = Signature.fromBytes(attWithIndex1.attestation.signature, true, true); + expect(fastAggregateVerify(attestationDataRoot, [sk1.toPublicKey(), sk2.toPublicKey()], aggregatedSignature)).toBe( + true + ); }); }); diff --git a/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts b/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts index 3cb5d496bf9b..507c78d560b6 100644 --- a/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/syncCommittee.test.ts @@ -1,6 +1,6 @@ -import bls from "@chainsafe/bls"; import {toHexString} from "@chainsafe/ssz"; import {describe, it, expect, beforeEach, beforeAll, afterEach, vi, MockedObject} from "vitest"; +import {SecretKey} from "@chainsafe/blst"; import {altair} from "@lodestar/types"; import {SyncCommitteeMessagePool} from "../../../../src/chain/opPools/index.js"; import {Clock} from "../../../../src/util/clock.js"; @@ -18,7 +18,7 @@ describe("chain / opPools / SyncCommitteeMessagePool", function () { const cutOffTime = 1; beforeAll(async () => { - const sk = bls.SecretKey.fromBytes(Buffer.alloc(32, 1)); + const sk = SecretKey.fromBytes(Buffer.alloc(32, 1)); syncCommittee = { slot, beaconBlockRoot, @@ -42,7 +42,7 @@ describe("chain / opPools / SyncCommitteeMessagePool", function () { clockStub.secFromSlot.mockReturnValue(0); let contribution = cache.getContribution(subcommitteeIndex, syncCommittee.slot, syncCommittee.beaconBlockRoot); expect(contribution).not.toBeNull(); - const newSecretKey = bls.SecretKey.fromBytes(Buffer.alloc(32, 2)); + const newSecretKey = SecretKey.fromBytes(Buffer.alloc(32, 2)); const newSyncCommittee: altair.SyncCommitteeMessage = { slot: syncCommittee.slot, beaconBlockRoot, diff --git a/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts b/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts index dd303673f61e..e34a5d006272 100644 --- a/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts +++ b/packages/beacon-node/test/unit/chain/opPools/syncCommitteeContribution.test.ts @@ -1,7 +1,6 @@ -import type {SecretKey} from "@chainsafe/bls/types"; -import bls from "@chainsafe/bls"; import {BitArray} from "@chainsafe/ssz"; import {describe, it, expect, beforeEach, beforeAll} from "vitest"; +import {SecretKey, Signature, fastAggregateVerify} from "@chainsafe/blst"; import {newFilledArray} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; import {SYNC_COMMITTEE_SIZE, SYNC_COMMITTEE_SUBNET_COUNT} from "@lodestar/params"; @@ -83,7 +82,7 @@ describe("aggregate", function () { let bestContributionBySubnet: Map; beforeAll(async () => { for (let i = 0; i < SYNC_COMMITTEE_SUBNET_COUNT; i++) { - sks.push(bls.SecretKey.fromBytes(Buffer.alloc(32, i + 1))); + sks.push(SecretKey.fromBytes(Buffer.alloc(32, i + 1))); } bestContributionBySubnet = new Map(); }); @@ -112,10 +111,10 @@ describe("aggregate", function () { renderBitArray(BitArray.fromBoolArray(expectSyncCommittees)) ); expect( - bls.verifyAggregate( - testSks.map((sk) => sk.toPublicKey().toBytes()), + fastAggregateVerify( blockRoot, - syncAggregate.syncCommitteeSignature + testSks.map((sk) => sk.toPublicKey()), + Signature.fromBytes(syncAggregate.syncCommitteeSignature) ) ).toBe(true); }); diff --git a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts index 652749492240..9ce121e976d0 100644 --- a/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts +++ b/packages/beacon-node/test/unit/chain/prepareNextSlot.test.ts @@ -19,7 +19,7 @@ describe("PrepareNextSlot scheduler", () => { let regenStub: MockedBeaconChain["regen"]; let loggerStub: MockedLogger; let beaconProposerCacheStub: MockedBeaconChain["beaconProposerCache"]; - let getForkStub: MockInstance<[number], ForkName>; + let getForkStub: MockInstance<(_: number) => ForkName>; let updateBuilderStatus: MockedBeaconChain["updateBuilderStatus"]; let executionEngineStub: MockedBeaconChain["executionEngine"]; const emitPayloadAttributes = true; diff --git a/packages/beacon-node/test/unit/chain/validation/attestation/validateGossipAttestationsSameAttData.test.ts b/packages/beacon-node/test/unit/chain/validation/attestation/validateGossipAttestationsSameAttData.test.ts index cc6dc26f1a36..24fae8a03f0b 100644 --- a/packages/beacon-node/test/unit/chain/validation/attestation/validateGossipAttestationsSameAttData.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/attestation/validateGossipAttestationsSameAttData.test.ts @@ -1,6 +1,5 @@ -import bls from "@chainsafe/bls"; -import type {PublicKey, SecretKey} from "@chainsafe/bls/types"; import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"; +import {PublicKey, SecretKey} from "@chainsafe/blst"; import {ForkName} from "@lodestar/params"; import {SignatureSetType} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; @@ -49,7 +48,7 @@ describe("validateGossipAttestationsSameAttData", () => { const bytes = new Uint8Array(32); const dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); dataView.setUint32(0, i + 1, true); - const secretKey = bls.SecretKey.fromBytes(bytes); + const secretKey = SecretKey.fromKeygen(bytes); const publicKey = secretKey.toPublicKey(); keypair = {secretKey, publicKey}; keypairs.set(i, keypair); diff --git a/packages/beacon-node/test/unit/chain/validation/block.test.ts b/packages/beacon-node/test/unit/chain/validation/block.test.ts index 74f5248dd4b6..d90ca4a54d24 100644 --- a/packages/beacon-node/test/unit/chain/validation/block.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/block.test.ts @@ -16,7 +16,7 @@ describe("gossip block validation", function () { let chain: MockedBeaconChain; let forkChoice: MockedBeaconChain["forkChoice"]; let regen: Mocked; - let verifySignature: Mock<[boolean]>; + let verifySignature: Mock<() => boolean>; let job: SignedBeaconBlock; const proposerIndex = 0; const clockSlot = 32; diff --git a/packages/beacon-node/test/unit/chain/validation/blsToExecutionChange.test.ts b/packages/beacon-node/test/unit/chain/validation/blsToExecutionChange.test.ts index f83b900beb69..8d5032ed3ec7 100644 --- a/packages/beacon-node/test/unit/chain/validation/blsToExecutionChange.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/blsToExecutionChange.test.ts @@ -1,7 +1,6 @@ import {digest} from "@chainsafe/as-sha256"; -import bls from "@chainsafe/bls"; -import {PointFormat} from "@chainsafe/bls/types"; import {describe, it, beforeEach, afterEach, vi} from "vitest"; +import {SecretKey} from "@chainsafe/blst"; import {config as defaultConfig} from "@lodestar/config/default"; import {computeSigningRoot} from "@lodestar/state-transition"; import {capella, ssz} from "@lodestar/types"; @@ -29,12 +28,12 @@ describe("validate bls to execution change", () => { // Validator has to be active for long enough stateEmpty.slot = defaultConfig.SHARD_COMMITTEE_PERIOD * SLOTS_PER_EPOCH; // A withdrawal key which we will keep same on the two vals we generate - const wsk = bls.SecretKey.fromKeygen(); + const wsk = SecretKey.fromKeygen(Buffer.alloc(32)); // Generate and add first val - const sk1 = bls.SecretKey.fromKeygen(); - const pubkey1 = sk1.toPublicKey().toBytes(PointFormat.compressed); - const fromBlsPubkey = wsk.toPublicKey().toBytes(PointFormat.compressed); + const sk1 = SecretKey.fromKeygen(Buffer.alloc(32, 1)); + const pubkey1 = sk1.toPublicKey().toBytes(); + const fromBlsPubkey = wsk.toPublicKey().toBytes(); const withdrawalCredentials = digest(fromBlsPubkey); withdrawalCredentials[0] = BLS_WITHDRAWAL_PREFIX; const validator = ssz.phase0.Validator.toViewDU({ @@ -50,8 +49,8 @@ describe("validate bls to execution change", () => { stateEmpty.validators[0] = validator; // Gen and add second val - const sk2 = bls.SecretKey.fromKeygen(); - const pubkey2 = sk2.toPublicKey().toBytes(PointFormat.compressed); + const sk2 = SecretKey.fromKeygen(Buffer.alloc(32, 2)); + const pubkey2 = sk2.toPublicKey().toBytes(); // Set the next validator to already eth1 credential const withdrawalCredentialsTwo = digest(fromBlsPubkey); withdrawalCredentialsTwo[0] = ETH1_ADDRESS_WITHDRAWAL_PREFIX; diff --git a/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts b/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts index 70dda2535521..ef4364abc366 100644 --- a/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/lightClientFinalityUpdate.test.ts @@ -2,6 +2,7 @@ import {describe, it, expect, beforeEach, afterEach, vi} from "vitest"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {altair, ssz} from "@lodestar/types"; import {computeTimeAtSlot} from "@lodestar/state-transition"; +import {RequiredSelective} from "@lodestar/utils"; import {validateLightClientFinalityUpdate} from "../../../../src/chain/validation/lightClientFinalityUpdate.js"; import {LightClientErrorCode} from "../../../../src/chain/errors/lightClientError.js"; import {IBeaconChain} from "../../../../src/chain/index.js"; @@ -30,7 +31,7 @@ describe("Light Client Finality Update validation", function () { } }); - function mockChain(): IBeaconChain { + function mockChain(): RequiredSelective { const chain = getMockedBeaconChain(); vi.spyOn(chain, "genesisTime", "get").mockReturnValue(0); return chain; diff --git a/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts b/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts index 386a08641273..b63a08757380 100644 --- a/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/lightClientOptimisticUpdate.test.ts @@ -2,6 +2,7 @@ import {describe, it, expect, beforeEach, afterEach, vi} from "vitest"; import {createChainForkConfig, defaultChainConfig} from "@lodestar/config"; import {altair, ssz} from "@lodestar/types"; import {computeTimeAtSlot} from "@lodestar/state-transition"; +import {RequiredSelective} from "@lodestar/utils"; import {validateLightClientOptimisticUpdate} from "../../../../src/chain/validation/lightClientOptimisticUpdate.js"; import {LightClientErrorCode} from "../../../../src/chain/errors/lightClientError.js"; import {IBeaconChain} from "../../../../src/chain/index.js"; @@ -30,7 +31,7 @@ describe("Light Client Optimistic Update validation", function () { } }); - function mockChain(): IBeaconChain { + function mockChain(): RequiredSelective { const chain = getMockedBeaconChain({config}); vi.spyOn(chain, "genesisTime", "get").mockReturnValue(0); vi.spyOn(chain.lightClientServer, "getOptimisticUpdate"); diff --git a/packages/beacon-node/test/unit/chain/validation/voluntaryExit.test.ts b/packages/beacon-node/test/unit/chain/validation/voluntaryExit.test.ts index 3966815c28ce..d556ff610961 100644 --- a/packages/beacon-node/test/unit/chain/validation/voluntaryExit.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/voluntaryExit.test.ts @@ -1,6 +1,5 @@ -import bls from "@chainsafe/bls"; -import {PointFormat} from "@chainsafe/bls/types"; import {describe, it, beforeEach, beforeAll, vi, afterEach} from "vitest"; +import {SecretKey} from "@chainsafe/blst"; import {config} from "@lodestar/config/default"; import { CachedBeaconStateAllForks, @@ -25,7 +24,7 @@ describe("validate voluntary exit", () => { let opPool: MockedBeaconChain["opPool"]; beforeAll(() => { - const sk = bls.SecretKey.fromKeygen(); + const sk = SecretKey.fromKeygen(Buffer.alloc(32)); const stateEmpty = ssz.phase0.BeaconState.defaultValue(); @@ -34,7 +33,7 @@ describe("validate voluntary exit", () => { // Add a validator that's active since genesis and ready to exit const validator = ssz.phase0.Validator.toViewDU({ - pubkey: sk.toPublicKey().toBytes(PointFormat.compressed), + pubkey: sk.toPublicKey().toBytes(), withdrawalCredentials: Buffer.alloc(32, 0), effectiveBalance: 32e9, slashed: false, diff --git a/packages/beacon-node/test/unit/executionEngine/http.test.ts b/packages/beacon-node/test/unit/executionEngine/http.test.ts index 27e9b86887ee..aa33c7dbbc40 100644 --- a/packages/beacon-node/test/unit/executionEngine/http.test.ts +++ b/packages/beacon-node/test/unit/executionEngine/http.test.ts @@ -9,6 +9,7 @@ import { serializeExecutionPayload, serializeExecutionPayloadBody, } from "../../../src/execution/engine/types.js"; +import {RpcPayload} from "../../../src/eth1/interface.js"; import {numToQuantity} from "../../../src/eth1/provider/utils.js"; describe("ExecutionEngine / http", () => { @@ -29,6 +30,10 @@ describe("ExecutionEngine / http", () => { const server = fastify({logger: false}); server.post("/", async (req) => { + if ((req.body as RpcPayload).method === "engine_getClientVersionV1") { + // Ignore client version requests + return []; + } reqJsonRpcPayload = req.body; delete (reqJsonRpcPayload as {id?: number}).id; return returnValue; diff --git a/packages/beacon-node/test/unit/util/graffiti.test.ts b/packages/beacon-node/test/unit/util/graffiti.test.ts index b1338181b324..0197e39c5e52 100644 --- a/packages/beacon-node/test/unit/util/graffiti.test.ts +++ b/packages/beacon-node/test/unit/util/graffiti.test.ts @@ -1,5 +1,6 @@ import {describe, it, expect} from "vitest"; -import {toGraffitiBuffer} from "../../../src/util/graffiti.js"; +import {getDefaultGraffiti, toGraffitiBuffer} from "../../../src/util/graffiti.js"; +import {ClientCode} from "../../../src/execution/index.js"; describe("Graffiti helper", () => { describe("toGraffitiBuffer", () => { @@ -26,4 +27,29 @@ describe("Graffiti helper", () => { }); } }); + + describe("getDefaultGraffiti", () => { + const executionClientVersion = {code: ClientCode.BU, name: "Besu", version: "24.1.1", commit: "9b0e38fa"}; + const consensusClientVersion = { + code: ClientCode.LS, + name: "Lodestar", + version: "v0.36.0/80c248b", + commit: "80c248bb", + }; // Sample output of getLodestarClientVersion() + + it("should return empty if private option is set", () => { + const result = getDefaultGraffiti(consensusClientVersion, executionClientVersion, {private: true}); + expect(result).toBe(""); + }); + + it("should return CL only info if EL client version is missing", () => { + const result = getDefaultGraffiti(consensusClientVersion, undefined, {private: false}); + expect(result).toBe("LS80c2"); + }); + + it("should return combined version codes and commits if executionClientVersion is provided", () => { + const result = getDefaultGraffiti(consensusClientVersion, executionClientVersion, {private: false}); + expect(result).toBe("BU9b0eLS80c2"); + }); + }); }); diff --git a/packages/beacon-node/test/unit/util/metadata.test.ts b/packages/beacon-node/test/unit/util/metadata.test.ts new file mode 100644 index 000000000000..4e732d93e5d0 --- /dev/null +++ b/packages/beacon-node/test/unit/util/metadata.test.ts @@ -0,0 +1,17 @@ +import {describe, it, expect} from "vitest"; +import {getLodestarClientVersion} from "../../../src/util/metadata.js"; +import {ClientCode} from "../../../src/execution/index.js"; + +describe("util / metadata", () => { + describe("getLodestarClientVersion", () => { + it("should return empty version and commit", () => { + const expected = {code: ClientCode.LS, name: "Lodestar", version: "", commit: ""}; + expect(getLodestarClientVersion()).toEqual(expected); + }); + it("should return full client info", () => { + const info = {version: "v0.36.0/80c248b", commit: "80c248bb392f512cc115d95059e22239a17bbd7d"}; // Version and long commit from readAndGetGitData() + const expected = {code: ClientCode.LS, name: "Lodestar", version: "v0.36.0/80c248b", commit: "80c248bb"}; + expect(getLodestarClientVersion(info)).toEqual(expected); + }); + }); +}); diff --git a/packages/beacon-node/test/unit/util/sszBytes.test.ts b/packages/beacon-node/test/unit/util/sszBytes.test.ts index bb5fc67a7ce6..4285f4ca88b5 100644 --- a/packages/beacon-node/test/unit/util/sszBytes.test.ts +++ b/packages/beacon-node/test/unit/util/sszBytes.test.ts @@ -4,7 +4,7 @@ import {fromHex, toHex} from "@lodestar/utils"; import { getAttDataBase64FromAttestationSerialized, getAttDataBase64FromSignedAggregateAndProofSerialized, - getAggregationBitsFromAttestationSerialized as getAggregationBitsFromAttestationSerialized, + getAggregationBitsFromAttestationSerialized, getBlockRootFromAttestationSerialized, getBlockRootFromSignedAggregateAndProofSerialized, getSlotFromAttestationSerialized, @@ -126,6 +126,15 @@ describe("aggregateAndProof SSZ serialized picking", () => { expect(getAttDataBase64FromSignedAggregateAndProofSerialized(Buffer.alloc(size))).toBeNull(); } }); + it("getSlotFromSignedAggregateAndProofSerialized - invalid data - large slots", () => { + const serialize = (slot: Slot): Uint8Array => { + const s = ssz.phase0.SignedAggregateAndProof.defaultValue(); + s.message.aggregate.data.slot = slot; + return ssz.phase0.SignedAggregateAndProof.serialize(s); + }; + expect(getSlotFromSignedAggregateAndProofSerialized(serialize(0xffffffff))).toBe(0xffffffff); + expect(getSlotFromSignedAggregateAndProofSerialized(serialize(0x0100000000))).toBeNull(); + }); }); describe("signedBeaconBlock SSZ serialized picking", () => { diff --git a/packages/beacon-node/test/utils/cache.ts b/packages/beacon-node/test/utils/cache.ts index 767956226776..9d5d3566c99d 100644 --- a/packages/beacon-node/test/utils/cache.ts +++ b/packages/beacon-node/test/utils/cache.ts @@ -1,4 +1,4 @@ -import type {SecretKey} from "@chainsafe/bls/types"; +import {SecretKey} from "@chainsafe/blst"; import {toHexString} from "@chainsafe/ssz"; export function memoOnce(fn: () => R): () => R { diff --git a/packages/beacon-node/test/utils/node/validator.ts b/packages/beacon-node/test/utils/node/validator.ts index c9a705ae3dc1..4ec60dcc8b4f 100644 --- a/packages/beacon-node/test/utils/node/validator.ts +++ b/packages/beacon-node/test/utils/node/validator.ts @@ -1,6 +1,6 @@ import tmp from "tmp"; import {vi} from "vitest"; -import type {SecretKey} from "@chainsafe/bls/types"; +import {SecretKey} from "@chainsafe/blst"; import {LevelDbController} from "@lodestar/db"; import {interopSecretKey} from "@lodestar/state-transition"; import {SlashingProtection, Validator, Signer, SignerType, ValidatorProposerConfig} from "@lodestar/validator"; diff --git a/packages/beacon-node/test/utils/state.ts b/packages/beacon-node/test/utils/state.ts index 10a653bf9498..1e9f614e8093 100644 --- a/packages/beacon-node/test/utils/state.ts +++ b/packages/beacon-node/test/utils/state.ts @@ -1,4 +1,4 @@ -import bls from "@chainsafe/bls"; +import {SecretKey} from "@chainsafe/blst"; import {config as minimalConfig} from "@lodestar/config/default"; import { BeaconStateAllForks, @@ -55,7 +55,7 @@ export function generateState( opts.validators ?? (withPubkey ? Array.from({length: numValidators}, (_, i) => { - const sk = bls.SecretKey.fromBytes(Buffer.alloc(32, i + 1)); + const sk = SecretKey.fromBytes(Buffer.alloc(32, i + 1)); return generateValidator({ ...validatorOpts, pubkey: sk.toPublicKey().toBytes(), diff --git a/packages/cli/package.json b/packages/cli/package.json index 30b446c9fd1b..6ce630057922 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/lodestar", - "version": "1.20.2", + "version": "1.21.0", "description": "Command line interface for lodestar", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -51,10 +51,9 @@ "blockchain" ], "dependencies": { - "@chainsafe/bls": "7.1.3", "@chainsafe/bls-keygen": "^0.4.0", - "@chainsafe/bls-keystore": "^3.0.1", - "@chainsafe/blst": "^0.2.11", + "@chainsafe/bls-keystore": "^3.1.0", + "@chainsafe/blst": "^2.0.3", "@chainsafe/discv5": "^9.0.0", "@chainsafe/enr": "^3.0.0", "@chainsafe/persistent-merkle-tree": "^0.7.1", @@ -63,17 +62,17 @@ "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", "@libp2p/peer-id-factory": "^4.1.0", - "@lodestar/api": "^1.20.2", - "@lodestar/beacon-node": "^1.20.2", - "@lodestar/config": "^1.20.2", - "@lodestar/db": "^1.20.2", - "@lodestar/light-client": "^1.20.2", - "@lodestar/logger": "^1.20.2", - "@lodestar/params": "^1.20.2", - "@lodestar/state-transition": "^1.20.2", - "@lodestar/types": "^1.20.2", - "@lodestar/utils": "^1.20.2", - "@lodestar/validator": "^1.20.2", + "@lodestar/api": "^1.21.0", + "@lodestar/beacon-node": "^1.21.0", + "@lodestar/config": "^1.21.0", + "@lodestar/db": "^1.21.0", + "@lodestar/light-client": "^1.21.0", + "@lodestar/logger": "^1.21.0", + "@lodestar/params": "^1.21.0", + "@lodestar/state-transition": "^1.21.0", + "@lodestar/types": "^1.21.0", + "@lodestar/utils": "^1.21.0", + "@lodestar/validator": "^1.21.0", "@multiformats/multiaddr": "^12.1.3", "deepmerge": "^4.3.1", "ethers": "^6.7.0", @@ -89,7 +88,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.20.2", + "@lodestar/test-utils": "^1.21.0", "@types/debug": "^4.1.7", "@types/got": "^9.6.12", "@types/inquirer": "^9.0.3", diff --git a/packages/cli/src/cmds/beacon/handler.ts b/packages/cli/src/cmds/beacon/handler.ts index e24089194185..d51af66e37d0 100644 --- a/packages/cli/src/cmds/beacon/handler.ts +++ b/packages/cli/src/cmds/beacon/handler.ts @@ -1,6 +1,5 @@ import path from "node:path"; import {getHeapStatistics} from "node:v8"; -import {Registry} from "prom-client"; import {ErrorAborted} from "@lodestar/utils"; import {LevelDbController} from "@lodestar/db"; import {BeaconNode, BeaconDb} from "@lodestar/beacon-node"; @@ -40,7 +39,7 @@ export async function beaconHandler(args: BeaconArgs & GlobalArgs): Promise = { @@ -140,6 +141,11 @@ export const beaconExtraOptions: CliCommandOptions = { description: "Attach the beacon node to `globalThis`. Useful to inspect a running beacon node.", type: "boolean", }, + + disableLightClientServer: { + description: "Disable light client server.", + type: "boolean", + }, }; type ENRArgs = { diff --git a/packages/cli/src/cmds/validator/blsToExecutionChange.ts b/packages/cli/src/cmds/validator/blsToExecutionChange.ts index 960662b108ea..3a2fabcc37eb 100644 --- a/packages/cli/src/cmds/validator/blsToExecutionChange.ts +++ b/packages/cli/src/cmds/validator/blsToExecutionChange.ts @@ -1,6 +1,5 @@ import {fromHexString} from "@chainsafe/ssz"; -import bls from "@chainsafe/bls"; -import {PointFormat} from "@chainsafe/bls/types"; +import {SecretKey} from "@chainsafe/blst"; import {computeSigningRoot} from "@lodestar/state-transition"; import {DOMAIN_BLS_TO_EXECUTION_CHANGE, ForkName} from "@lodestar/params"; import {createBeaconConfig} from "@lodestar/config"; @@ -68,8 +67,8 @@ like to choose for BLS To Execution Change.", throw new Error(`Validator pubkey ${publicKey} not found in state`); } - const blsPrivkey = bls.SecretKey.fromBytes(fromHexString(args.fromBlsPrivkey)); - const fromBlsPubkey = blsPrivkey.toPublicKey().toBytes(PointFormat.compressed); + const blsPrivkey = SecretKey.fromBytes(fromHexString(args.fromBlsPrivkey)); + const fromBlsPubkey = blsPrivkey.toPublicKey().toBytes(); const blsToExecutionChange: capella.BLSToExecutionChange = { validatorIndex: validator.index, diff --git a/packages/cli/src/cmds/validator/handler.ts b/packages/cli/src/cmds/validator/handler.ts index fdd93f1ebfc9..01c15126b796 100644 --- a/packages/cli/src/cmds/validator/handler.ts +++ b/packages/cli/src/cmds/validator/handler.ts @@ -19,7 +19,7 @@ import { import {getNodeLogger} from "@lodestar/logger/node"; import {getBeaconConfigFromArgs} from "../../config/index.js"; import {GlobalArgs} from "../../options/index.js"; -import {YargsError, cleanOldLogFiles, getDefaultGraffiti, mkdir, parseLoggerArgs} from "../../util/index.js"; +import {YargsError, cleanOldLogFiles, mkdir, parseLoggerArgs} from "../../util/index.js"; import {onGracefulShutdown, parseFeeRecipient, parseProposerConfig} from "../../util/index.js"; import {getVersionData} from "../../util/version.js"; import {parseBuilderSelection, parseBuilderBoostFactor} from "../../util/proposerConfig.js"; @@ -226,7 +226,7 @@ function getProposerConfigFromArgs( }: {persistedKeysBackend: IPersistedKeysBackend; accountPaths: {proposerDir: string}} ): ValidatorProposerConfig { const defaultConfig = { - graffiti: args.graffiti ?? getDefaultGraffiti(), + graffiti: args.graffiti, strictFeeRecipientCheck: args.strictFeeRecipientCheck, feeRecipient: args.suggestedFeeRecipient ? parseFeeRecipient(args.suggestedFeeRecipient) : undefined, builder: { diff --git a/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts b/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts index 076da70adcdb..0ce0646c978a 100644 --- a/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts +++ b/packages/cli/src/cmds/validator/keymanager/decryptKeystoreDefinitions.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import bls from "@chainsafe/bls"; +import {SecretKey} from "@chainsafe/blst"; import {Keystore} from "@chainsafe/bls-keystore"; import {SignerLocal, SignerType} from "@lodestar/validator"; import {LogLevel, Logger} from "@lodestar/utils"; @@ -73,7 +73,7 @@ export async function decryptKeystoreDefinitions( (secretKeyBytes: Uint8Array) => { const signer: SignerLocal = { type: SignerType.Local, - secretKey: bls.SecretKey.fromBytes(secretKeyBytes), + secretKey: SecretKey.fromBytes(secretKeyBytes), }; signers[index] = signer; @@ -107,7 +107,7 @@ export async function decryptKeystoreDefinitions( const signer: SignerLocal = { type: SignerType.Local, - secretKey: bls.SecretKey.fromBytes(secretKeyBytes), + secretKey: SecretKey.fromBytes(secretKeyBytes), }; signers[index] = signer; diff --git a/packages/cli/src/cmds/validator/keymanager/impl.ts b/packages/cli/src/cmds/validator/keymanager/impl.ts index 36ded66976b1..2915c125eac2 100644 --- a/packages/cli/src/cmds/validator/keymanager/impl.ts +++ b/packages/cli/src/cmds/validator/keymanager/impl.ts @@ -1,6 +1,6 @@ -import bls from "@chainsafe/bls"; import {Keystore} from "@chainsafe/bls-keystore"; import {fromHexString} from "@chainsafe/ssz"; +import {SecretKey} from "@chainsafe/blst"; import { DeleteRemoteKeyStatus, DeletionStatus, @@ -60,7 +60,11 @@ export class KeymanagerApi implements Api { } async getGraffiti({pubkey}: {pubkey: PubkeyHex}): ReturnType { - return {data: {pubkey, graffiti: this.validator.validatorStore.getGraffiti(pubkey)}}; + const graffiti = this.validator.validatorStore.getGraffiti(pubkey); + if (graffiti === undefined) { + throw new ApiError(404, `No graffiti for pubkey ${pubkey}`); + } + return {data: {pubkey, graffiti}}; } async setGraffiti({pubkey, graffiti}: GraffitiData): ReturnType { @@ -149,7 +153,7 @@ export class KeymanagerApi implements Api { decryptKeystores.queue( {keystoreStr, password}, async (secretKeyBytes: Uint8Array) => { - const secretKey = bls.SecretKey.fromBytes(secretKeyBytes); + const secretKey = SecretKey.fromBytes(secretKeyBytes); // Persist the key to disk for restarts, before adding to in-memory store // If the keystore exist and has a lock it will throw diff --git a/packages/cli/src/cmds/validator/keymanager/keystoreCache.ts b/packages/cli/src/cmds/validator/keymanager/keystoreCache.ts index 1f1c6cd6e79f..85b1702892ee 100644 --- a/packages/cli/src/cmds/validator/keymanager/keystoreCache.ts +++ b/packages/cli/src/cmds/validator/keymanager/keystoreCache.ts @@ -1,8 +1,7 @@ import fs from "node:fs"; import path from "node:path"; -import bls from "@chainsafe/bls"; import {Keystore} from "@chainsafe/bls-keystore"; -import {PointFormat} from "@chainsafe/bls/types"; +import {SecretKey} from "@chainsafe/blst"; import {SignerLocal, SignerType} from "@lodestar/validator"; import {fromHex, toHex} from "@lodestar/utils"; import {writeFile600Perm} from "../../../util/file.js"; @@ -40,8 +39,8 @@ export async function loadKeystoreCache( const result: SignerLocal[] = []; for (const [index, k] of keystores.entries()) { const secretKeyBytes = Uint8Array.prototype.slice.call(secretKeyConcatenatedBytes, index * 32, (index + 1) * 32); - const secretKey = bls.SecretKey.fromBytes(secretKeyBytes); - const publicKey = secretKey.toPublicKey().toBytes(PointFormat.compressed); + const secretKey = SecretKey.fromBytes(secretKeyBytes); + const publicKey = secretKey.toPublicKey().toBytes(); if (toHex(publicKey) !== toHex(fromHex(k.pubkey))) { throw new Error( diff --git a/packages/cli/src/cmds/validator/options.ts b/packages/cli/src/cmds/validator/options.ts index fc7cb197c5aa..aaa0e96d25a7 100644 --- a/packages/cli/src/cmds/validator/options.ts +++ b/packages/cli/src/cmds/validator/options.ts @@ -200,7 +200,6 @@ export const validatorOptions: CliCommandOptions = { graffiti: { description: "Specify your custom graffiti to be included in blocks (plain UTF8 text, 32 characters max)", - // Don't use a default here since it should be computed only if necessary by getDefaultGraffiti() type: "string", }, diff --git a/packages/cli/src/cmds/validator/signers/index.ts b/packages/cli/src/cmds/validator/signers/index.ts index 95daf7e69b0d..c155b52d186a 100644 --- a/packages/cli/src/cmds/validator/signers/index.ts +++ b/packages/cli/src/cmds/validator/signers/index.ts @@ -1,7 +1,6 @@ import path from "node:path"; -import bls from "@chainsafe/bls"; import {deriveEth2ValidatorKeys, deriveKeyFromMnemonic} from "@chainsafe/bls-keygen"; -import {toHexString} from "@chainsafe/ssz"; +import {SecretKey} from "@chainsafe/blst"; import {interopSecretKey} from "@lodestar/state-transition"; import {externalSignerGetKeys, Signer, SignerType} from "@lodestar/validator"; import {LogLevel, Logger, isValidHttpUrl} from "@lodestar/utils"; @@ -72,7 +71,7 @@ export async function getSignersFromArgs( const indexes = parseRange(args.mnemonicIndexes); return indexes.map((index) => ({ type: SignerType.Local, - secretKey: bls.SecretKey.fromBytes(deriveEth2ValidatorKeys(masterSK, index).signing), + secretKey: SecretKey.fromBytes(deriveEth2ValidatorKeys(masterSK, index).signing), })); } @@ -150,7 +149,7 @@ export async function getSignersFromArgs( export function getSignerPubkeyHex(signer: Signer): string { switch (signer.type) { case SignerType.Local: - return toHexString(signer.secretKey.toPublicKey().toBytes()); + return signer.secretKey.toPublicKey().toHex(); case SignerType.Remote: return signer.pubkey; diff --git a/packages/cli/src/cmds/validator/signers/logSigners.ts b/packages/cli/src/cmds/validator/signers/logSigners.ts index 47b0edc3a4d8..20aa50d25ff6 100644 --- a/packages/cli/src/cmds/validator/signers/logSigners.ts +++ b/packages/cli/src/cmds/validator/signers/logSigners.ts @@ -1,5 +1,5 @@ import {Signer, SignerLocal, SignerRemote, SignerType} from "@lodestar/validator"; -import {LogLevel, Logger, toSafePrintableUrl} from "@lodestar/utils"; +import {LogLevel, Logger, toPrintableUrl} from "@lodestar/utils"; import {YargsError} from "../../../util/errors.js"; import {IValidatorCliArgs} from "../options.js"; @@ -29,7 +29,7 @@ export function logSigners(logger: Pick, signers: Signer[ } for (const {url, pubkeys} of groupRemoteSignersByUrl(remoteSigners)) { - logger.info(`Remote signers on URL: ${toSafePrintableUrl(url)}`); + logger.info(`Remote signers on URL: ${toPrintableUrl(url)}`); for (const pubkey of pubkeys) { logger.info(pubkey); } diff --git a/packages/cli/src/cmds/validator/voluntaryExit.ts b/packages/cli/src/cmds/validator/voluntaryExit.ts index a08f33eb6bad..279076b619f2 100644 --- a/packages/cli/src/cmds/validator/voluntaryExit.ts +++ b/packages/cli/src/cmds/validator/voluntaryExit.ts @@ -1,5 +1,5 @@ import inquirer from "inquirer"; -import bls from "@chainsafe/bls"; +import {Signature} from "@chainsafe/blst"; import { computeEpochAtSlot, computeSigningRoot, @@ -8,7 +8,7 @@ import { } from "@lodestar/state-transition"; import {createBeaconConfig, BeaconConfig} from "@lodestar/config"; import {phase0, ssz, ValidatorIndex, Epoch} from "@lodestar/types"; -import {CliCommand, toHex} from "@lodestar/utils"; +import {CliCommand, fromHex, toHex} from "@lodestar/utils"; import {externalSignerPostSignature, SignableMessageType, Signer, SignerType} from "@lodestar/validator"; import {ApiClient, getClient} from "@lodestar/api"; import {ensure0xPrefix, YargsError, wrapError} from "../../util/index.js"; @@ -161,7 +161,7 @@ async function processVoluntaryExit( data: voluntaryExit, type: SignableMessageType.VOLUNTARY_EXIT, }); - signature = bls.Signature.fromHex(signatureHex); + signature = Signature.fromBytes(fromHex(signatureHex)); break; } default: diff --git a/packages/cli/src/util/format.ts b/packages/cli/src/util/format.ts index e673fa4d4408..01c2753193a4 100644 --- a/packages/cli/src/util/format.ts +++ b/packages/cli/src/util/format.ts @@ -1,5 +1,4 @@ -import bls from "@chainsafe/bls"; -import {CoordType} from "@chainsafe/blst"; +import {PublicKey} from "@chainsafe/blst"; import {fromHexString} from "@chainsafe/ssz"; /** @@ -52,7 +51,7 @@ export function parseRange(range: string): number[] { export function assertValidPubkeysHex(pubkeysHex: string[]): void { for (const pubkeyHex of pubkeysHex) { const pubkeyBytes = fromHexString(pubkeyHex); - bls.PublicKey.fromBytes(pubkeyBytes, CoordType.jacobian, true); + PublicKey.fromBytes(pubkeyBytes, true); } } diff --git a/packages/cli/src/util/graffiti.ts b/packages/cli/src/util/graffiti.ts deleted file mode 100644 index 1d859ea8d9b8..000000000000 --- a/packages/cli/src/util/graffiti.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {getVersionData} from "./version.js"; - -const lodestarPackageName = "Lodestar"; - -/** - * Computes a default graffiti fetching dynamically the package info. - * @returns a string containing package name and version. - */ -export function getDefaultGraffiti(): string { - try { - const {version} = getVersionData(); - return `${lodestarPackageName}-${version}`; - } catch (e) { - // eslint-disable-next-line no-console - console.error("Error guessing lodestar version", e as Error); - return lodestarPackageName; - } -} diff --git a/packages/cli/src/util/index.ts b/packages/cli/src/util/index.ts index 3d94977f5fb7..579587317e02 100644 --- a/packages/cli/src/util/index.ts +++ b/packages/cli/src/util/index.ts @@ -4,7 +4,6 @@ export * from "./file.js"; export * from "./format.js"; export * from "./fs.js"; export * from "./gitData/index.js"; -export * from "./graffiti.js"; export * from "./logger.js"; export * from "./object.js"; export * from "./passphrase.js"; diff --git a/packages/cli/test/e2e/blsToExecutionchange.test.ts b/packages/cli/test/e2e/blsToExecutionchange.test.ts index b273ab90c996..51720e424c7e 100644 --- a/packages/cli/test/e2e/blsToExecutionchange.test.ts +++ b/packages/cli/test/e2e/blsToExecutionchange.test.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import {afterAll, describe, it, vi, beforeEach, afterEach} from "vitest"; +import {describe, it, vi, onTestFinished} from "vitest"; import {toHexString} from "@chainsafe/ssz"; import {sleep, retry} from "@lodestar/utils"; import {getClient} from "@lodestar/api"; @@ -26,8 +26,11 @@ describe("bLSToExecutionChange cmd", function () { // Speed up test to make genesis happen faster "--params.SECONDS_PER_SLOT=2", ], - {pipeStdioToParent: true, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}} + {pipeStdioToParent: true, logPrefix: "dev"} ); + onTestFinished(async () => { + await stopChildProcess(devBnProc); + }); // Exit early if process exits devBnProc.on("exit", (code) => { diff --git a/packages/cli/test/e2e/importFromFsDirect.test.ts b/packages/cli/test/e2e/importFromFsDirect.test.ts index df53e0f973bb..644635bc0ffe 100644 --- a/packages/cli/test/e2e/importFromFsDirect.test.ts +++ b/packages/cli/test/e2e/importFromFsDirect.test.ts @@ -37,21 +37,23 @@ describe("import from fs same cmd as validate", function () { // Check that there are not keys loaded without adding extra args `--importKeystores` it("run 'validator' there are no keys loaded", async () => { - const {keymanagerClient} = await startValidatorWithKeyManager([], { + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], { dataDir, logPrefix: "case-1", }); await expectKeys(keymanagerClient, [], "Wrong listKeys response data"); + await stopValidator(); }); // Run validator with extra arguments to load keystores in same step it("run 'validator' check keys are loaded", async () => { - const {keymanagerClient} = await startValidatorWithKeyManager( + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager( [`--importKeystores=${importFromDir}`, `--importKeystoresPassword=${passphraseFilepath}`], {dataDir, logPrefix: "case-2"} ); await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys response data"); + await stopValidator(); }); }); diff --git a/packages/cli/test/e2e/importFromFsPreStep.test.ts b/packages/cli/test/e2e/importFromFsPreStep.test.ts index ae9ac3321a05..7eebf2d4946e 100644 --- a/packages/cli/test/e2e/importFromFsPreStep.test.ts +++ b/packages/cli/test/e2e/importFromFsPreStep.test.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import {describe, it, expect, beforeAll, vi} from "vitest"; +import {describe, it, expect, beforeAll, vi, onTestFinished} from "vitest"; import {rimraf} from "rimraf"; import {execCliCommand} from "@lodestar/test-utils"; import {getKeystoresStr} from "@lodestar/test-utils"; @@ -59,7 +59,10 @@ describe("import from fs then validate", function () { }); it("run 'validator' check keys are loaded", async function () { - const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir}); + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys response data"); }); diff --git a/packages/cli/test/e2e/importKeystoresFromApi.test.ts b/packages/cli/test/e2e/importKeystoresFromApi.test.ts index 968e8ca980ce..b7abe1e8d293 100644 --- a/packages/cli/test/e2e/importKeystoresFromApi.test.ts +++ b/packages/cli/test/e2e/importKeystoresFromApi.test.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import {describe, it, expect, beforeAll, vi, afterAll, beforeEach, afterEach} from "vitest"; +import {describe, it, expect, beforeAll, vi, onTestFinished} from "vitest"; import {rimraf} from "rimraf"; import {DeletionStatus, getClient, ImportStatus} from "@lodestar/api/keymanager"; import {config} from "@lodestar/config/default"; @@ -55,7 +55,11 @@ describe("import keystores from api", function () { const slashingProtectionStr = JSON.stringify(slashingProtection); it("run 'validator' and import remote keys from API", async () => { - const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir}); + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); + // Produce and encrypt keystores const keystoresStr = await getKeystoresStr(passphrase, secretKeys); @@ -92,7 +96,6 @@ describe("import keystores from api", function () { // Attempt to run a second process and expect the keystore lock to throw const validator = await spawnCliCommand("packages/cli/bin/lodestar.js", ["validator", "--dataDir", dataDir], { logPrefix: "vc-2", - testContext: {beforeEach, afterEach, afterAll}, }); await new Promise((resolve, reject) => { @@ -117,8 +120,10 @@ describe("import keystores from api", function () { }); it("run 'validator' check keys are loaded + delete", async function () { - const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir}); - + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); // Check that keys imported in previous it() are still there await expectKeys(keymanagerClient, pubkeys, "Wrong listKeys before deleting"); @@ -135,13 +140,20 @@ describe("import keystores from api", function () { }); it("different process check no keys are loaded", async function () { - const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir}); + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); + // After deleting there should be no keys await expectKeys(keymanagerClient, [], "Wrong listKeys"); }); it("reject calls without bearerToken", async function () { - await startValidatorWithKeyManager([], {dataDir}); + const {stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); const keymanagerClientNoAuth = getClient( {baseUrl: "http://localhost:38011", globalInit: {bearerToken: undefined}}, diff --git a/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts b/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts index c5638195d809..0d7e4aa58da3 100644 --- a/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts +++ b/packages/cli/test/e2e/importRemoteKeysFromApi.test.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import {describe, it, expect, beforeAll, vi} from "vitest"; +import {describe, it, expect, beforeAll, vi, onTestFinished} from "vitest"; import {rimraf} from "rimraf"; import {ApiClient, DeleteRemoteKeyStatus, getClient, ImportRemoteKeyStatus} from "@lodestar/api/keymanager"; import {config} from "@lodestar/config/default"; @@ -33,7 +33,10 @@ describe("import remoteKeys from api", function () { const pubkeysToAdd = [cachedPubkeysHex[0], cachedPubkeysHex[1]]; it("run 'validator' and import remote keys from API", async () => { - const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir}); + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); // Wrap in retry since the API may not be listening yet await expectKeys(keymanagerClient, [], "Wrong listRemoteKeys before importing"); @@ -63,7 +66,11 @@ describe("import remoteKeys from api", function () { }); it("run 'validator' check keys are loaded + delete", async function () { - const {keymanagerClient} = await startValidatorWithKeyManager([], {dataDir}); + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); + // Check that keys imported in previous it() are still there await expectKeys(keymanagerClient, pubkeysToAdd, "Wrong listRemoteKeys before deleting"); @@ -80,7 +87,11 @@ describe("import remoteKeys from api", function () { }); it("reject calls without bearerToken", async function () { - await startValidatorWithKeyManager([], {dataDir}); + const {stopValidator} = await startValidatorWithKeyManager([], {dataDir}); + onTestFinished(async () => { + await stopValidator(); + }); + const keymanagerUrl = "http://localhost:38011"; const keymanagerClientNoAuth = getClient({baseUrl: keymanagerUrl, globalInit: {bearerToken: undefined}}, {config}); const res = await keymanagerClientNoAuth.listRemoteKeys(); diff --git a/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts b/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts index 711997a86627..ffeb40460f40 100644 --- a/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts +++ b/packages/cli/test/e2e/propserConfigfromKeymanager.test.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import {describe, it, beforeAll, vi} from "vitest"; +import {describe, it, beforeAll, vi, onTestFinished} from "vitest"; import {rimraf} from "rimraf"; import {ImportStatus} from "@lodestar/api/keymanager"; import {Interchange} from "@lodestar/validator"; @@ -49,9 +49,16 @@ describe("import keystores from api, test DefaultProposerConfig", function () { const slashingProtectionStr = JSON.stringify(slashingProtection); it("1 . run 'validator' import keys from API, getdefaultfeeRecipient", async () => { - const {keymanagerClient} = await startValidatorWithKeyManager([`--graffiti ${defaultOptions.graffiti}`], { - dataDir, + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager( + [`--graffiti ${defaultOptions.graffiti}`], + { + dataDir, + } + ); + onTestFinished(async () => { + await stopValidator(); }); + // Produce and encrypt keystores // Import test keys const keystoresStr = await getKeystoresStr(passphrase, secretKeys); @@ -108,8 +115,14 @@ describe("import keystores from api, test DefaultProposerConfig", function () { }); it("2 . run 'validator' Check last feeRecipient and gasLimit persists", async () => { - const {keymanagerClient} = await startValidatorWithKeyManager([`--graffiti ${defaultOptions.graffiti}`], { - dataDir, + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager( + [`--graffiti ${defaultOptions.graffiti}`], + { + dataDir, + } + ); + onTestFinished(async () => { + await stopValidator(); }); // next time check edited feeRecipient persists @@ -164,8 +177,14 @@ describe("import keystores from api, test DefaultProposerConfig", function () { }); it("3 . run 'validator' FeeRecipient and GasLimit should be default after delete", async () => { - const {keymanagerClient} = await startValidatorWithKeyManager([`--graffiti ${defaultOptions.graffiti}`], { - dataDir, + const {keymanagerClient, stopValidator} = await startValidatorWithKeyManager( + [`--graffiti ${defaultOptions.graffiti}`], + { + dataDir, + } + ); + onTestFinished(async () => { + await stopValidator(); }); const feeRecipient0 = (await keymanagerClient.listFeeRecipient({pubkey: pubkeys[0]})).value(); diff --git a/packages/cli/test/e2e/runDevCmd.test.ts b/packages/cli/test/e2e/runDevCmd.test.ts index 8e1b8b04e257..68dcca156d88 100644 --- a/packages/cli/test/e2e/runDevCmd.test.ts +++ b/packages/cli/test/e2e/runDevCmd.test.ts @@ -1,8 +1,8 @@ -import {describe, it, vi, beforeEach, afterEach, afterAll} from "vitest"; +import {describe, it, vi, onTestFinished} from "vitest"; import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {retry} from "@lodestar/utils"; -import {spawnCliCommand} from "@lodestar/test-utils"; +import {spawnCliCommand, stopChildProcess} from "@lodestar/test-utils"; describe("Run dev command", function () { vi.setConfig({testTimeout: 30_000}); @@ -13,8 +13,11 @@ describe("Run dev command", function () { const devProc = await spawnCliCommand( "packages/cli/bin/lodestar.js", ["dev", "--reset", "--startValidators=0..7", `--rest.port=${beaconPort}`], - {pipeStdioToParent: true, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}} + {pipeStdioToParent: true, logPrefix: "dev"} ); + onTestFinished(async () => { + await stopChildProcess(devProc); + }); // Exit early if process exits devProc.on("exit", (code) => { diff --git a/packages/cli/test/e2e/voluntaryExit.test.ts b/packages/cli/test/e2e/voluntaryExit.test.ts index f8c9150790f3..49499e891731 100644 --- a/packages/cli/test/e2e/voluntaryExit.test.ts +++ b/packages/cli/test/e2e/voluntaryExit.test.ts @@ -1,10 +1,10 @@ import path from "node:path"; -import {afterAll, describe, it, vi, beforeEach, afterEach} from "vitest"; +import {describe, it, vi, onTestFinished} from "vitest"; import {retry} from "@lodestar/utils"; import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; import {interopSecretKey} from "@lodestar/state-transition"; -import {spawnCliCommand, execCliCommand} from "@lodestar/test-utils"; +import {spawnCliCommand, execCliCommand, stopChildProcess} from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; describe("voluntaryExit cmd", function () { @@ -28,14 +28,11 @@ describe("voluntaryExit cmd", function () { // Allow voluntary exists to be valid immediately "--params.SHARD_COMMITTEE_PERIOD=0", ], - {pipeStdioToParent: true, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}} + {pipeStdioToParent: true, logPrefix: "dev"} ); - // Exit early if process exits - devBnProc.on("exit", (code) => { - if (code !== null && code > 0) { - throw new Error(`devBnProc process exited with code ${code}`); - } + onTestFinished(async () => { + await stopChildProcess(devBnProc, "SIGINT"); }); const baseUrl = `http://127.0.0.1:${restPort}`; diff --git a/packages/cli/test/e2e/voluntaryExitFromApi.test.ts b/packages/cli/test/e2e/voluntaryExitFromApi.test.ts index ccd1dfeeba37..664091d5c520 100644 --- a/packages/cli/test/e2e/voluntaryExitFromApi.test.ts +++ b/packages/cli/test/e2e/voluntaryExitFromApi.test.ts @@ -1,10 +1,10 @@ import path from "node:path"; -import {describe, it, vi, expect, afterAll, beforeEach, afterEach} from "vitest"; +import {describe, it, vi, expect, onTestFinished} from "vitest"; import {getClient} from "@lodestar/api"; import {getClient as getKeymanagerClient} from "@lodestar/api/keymanager"; import {config} from "@lodestar/config/default"; import {interopSecretKey} from "@lodestar/state-transition"; -import {spawnCliCommand} from "@lodestar/test-utils"; +import {spawnCliCommand, stopChildProcess} from "@lodestar/test-utils"; import {retry} from "@lodestar/utils"; import {testFilesDir} from "../utils.js"; @@ -37,8 +37,11 @@ describe("voluntary exit from api", function () { // Disable bearer token auth to simplify testing "--keymanager.auth=false", ], - {pipeStdioToParent: false, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}} + {pipeStdioToParent: false, logPrefix: "dev"} ); + onTestFinished(async () => { + await stopChildProcess(devProc); + }); // Exit early if process exits devProc.on("exit", (code) => { diff --git a/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts b/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts index ab0c1e1e9ee3..a9cf2f48a168 100644 --- a/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts +++ b/packages/cli/test/e2e/voluntaryExitRemoteSigner.test.ts @@ -1,5 +1,5 @@ import path from "node:path"; -import {describe, it, beforeAll, afterAll, beforeEach, afterEach, vi} from "vitest"; +import {describe, it, beforeAll, afterAll, vi, onTestFinished} from "vitest"; import {retry} from "@lodestar/utils"; import {getClient} from "@lodestar/api"; import {config} from "@lodestar/config/default"; @@ -10,6 +10,7 @@ import { startExternalSigner, StartedExternalSigner, getKeystoresStr, + stopChildProcess, } from "@lodestar/test-utils"; import {testFilesDir} from "../utils.js"; @@ -50,8 +51,11 @@ describe("voluntaryExit using remote signer", function () { // Allow voluntary exists to be valid immediately "--params.SHARD_COMMITTEE_PERIOD=0", ], - {pipeStdioToParent: false, logPrefix: "dev", testContext: {beforeEach, afterEach, afterAll}} + {pipeStdioToParent: false, logPrefix: "dev"} ); + onTestFinished(async () => { + await stopChildProcess(devBnProc); + }); // Exit early if process exits devBnProc.on("exit", (code) => { diff --git a/packages/cli/test/sim/endpoints.test.ts b/packages/cli/test/sim/endpoints.test.ts index a40a18e379eb..07cd5fec0cc4 100644 --- a/packages/cli/test/sim/endpoints.test.ts +++ b/packages/cli/test/sim/endpoints.test.ts @@ -107,8 +107,8 @@ await env.tracker.assert( await env.tracker.assert("BN Not Synced", async () => { const expectedSyncStatus: routes.node.SyncingStatus = { - headSlot: "2", - syncDistance: "0", + headSlot: 2, + syncDistance: 0, isSyncing: false, isOptimistic: false, elOffline: false, diff --git a/packages/cli/test/unit/cmds/validator/keymanager/keystoreCache.test.ts b/packages/cli/test/unit/cmds/validator/keymanager/keystoreCache.test.ts index 0f4d0126bb20..8d3dbb989b5d 100644 --- a/packages/cli/test/unit/cmds/validator/keymanager/keystoreCache.test.ts +++ b/packages/cli/test/unit/cmds/validator/keymanager/keystoreCache.test.ts @@ -3,7 +3,7 @@ import {randomBytes} from "node:crypto"; import {describe, it, expect, beforeEach, vi} from "vitest"; import tmp from "tmp"; import {Keystore} from "@chainsafe/bls-keystore"; -import bls from "@chainsafe/bls"; +import {SecretKey} from "@chainsafe/blst"; import {interopSecretKey} from "@lodestar/state-transition"; import {SignerLocal, SignerType} from "@lodestar/validator"; import {loadKeystoreCache, writeKeystoreCache} from "../../../../../src/cmds/validator/keymanager/keystoreCache.js"; @@ -27,7 +27,7 @@ describe("keystoreCache", () => { keystoreCacheFile = tmp.tmpNameSync({postfix: ".cache"}); for (let i = 0; i < numberOfSigners; i++) { - const secretKey = bls.SecretKey.fromBytes(interopSecretKey(i).toBytes()); + const secretKey = SecretKey.fromBytes(interopSecretKey(i).toBytes()); const keystorePath = tmp.tmpNameSync({postfix: ".json"}); const password = secretKey.toHex(); const keystore = await Keystore.create( diff --git a/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts b/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts index 69039accb182..9659cc34e7bf 100644 --- a/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts +++ b/packages/cli/test/utils/crucible/assertions/nodeAssertion.ts @@ -1,5 +1,6 @@ -import type {SecretKey} from "@chainsafe/bls/types"; +import {SecretKey} from "@chainsafe/blst"; import {routes} from "@lodestar/api/beacon"; +import {toHex} from "@lodestar/utils"; import {AssertionResult, ValidatorClientKeys, Assertion, ValidatorClient} from "../interfaces.js"; import {arrayEquals} from "../utils/index.js"; import {neverMatcher} from "./matchers.js"; @@ -38,7 +39,7 @@ export const nodeAssertion: Assertion<"node", {health: number; keyManagerKeys: s } const expectedPublicKeys = node.validator - ? getAllKeys(node.validator.keys).map((k) => k.toPublicKey().toHex()) + ? getAllKeys(node.validator.keys).map((k) => toHex(k.toPublicKey().toBytes())) : []; if (!arrayEquals(keyManagerKeys.sort(), expectedPublicKeys.sort())) { diff --git a/packages/cli/test/utils/crucible/externalSignerServer.ts b/packages/cli/test/utils/crucible/externalSignerServer.ts index 25071d1ee545..bd8576d214c2 100644 --- a/packages/cli/test/utils/crucible/externalSignerServer.ts +++ b/packages/cli/test/utils/crucible/externalSignerServer.ts @@ -1,6 +1,6 @@ -import type {SecretKey} from "@chainsafe/bls/types"; import {fromHexString} from "@chainsafe/ssz"; import {fastify, FastifyInstance} from "fastify"; +import {SecretKey} from "@chainsafe/blst"; import {EXTERNAL_SIGNER_BASE_PORT} from "./constants.js"; /* eslint-disable no-console */ diff --git a/packages/cli/test/utils/crucible/interfaces.ts b/packages/cli/test/utils/crucible/interfaces.ts index 44358561cea5..db4b1f3b8901 100644 --- a/packages/cli/test/utils/crucible/interfaces.ts +++ b/packages/cli/test/utils/crucible/interfaces.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import {ChildProcess} from "node:child_process"; -import type {SecretKey} from "@chainsafe/bls/types"; import {Web3} from "web3"; +import {SecretKey} from "@chainsafe/blst"; import {ApiClient} from "@lodestar/api"; import {ApiClient as KeyManagerApi} from "@lodestar/api/keymanager"; import {ChainForkConfig} from "@lodestar/config"; diff --git a/packages/cli/test/utils/validator.ts b/packages/cli/test/utils/validator.ts index dc3ef754cc74..139683d5dd6b 100644 --- a/packages/cli/test/utils/validator.ts +++ b/packages/cli/test/utils/validator.ts @@ -1,5 +1,4 @@ import childProcess from "node:child_process"; -import {afterEach} from "vitest"; import {retry} from "@lodestar/utils"; import {ApiClient, getClient} from "@lodestar/api/keymanager"; import {config} from "@lodestar/config/default"; @@ -73,8 +72,6 @@ export async function startValidatorWithKeyManager( await gracefullyStopChildProcess(validatorProc, 3000); }; - afterEach(stopValidator); - return { validator: validatorProc, stopValidator, diff --git a/packages/config/package.json b/packages/config/package.json index 8d5fd3d80c35..cd4157f9d073 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/config", - "version": "1.20.2", + "version": "1.21.0", "description": "Chain configuration required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -65,7 +65,7 @@ ], "dependencies": { "@chainsafe/ssz": "^0.15.1", - "@lodestar/params": "^1.20.2", - "@lodestar/types": "^1.20.2" + "@lodestar/params": "^1.21.0", + "@lodestar/types": "^1.21.0" } } diff --git a/packages/config/src/chainConfig/networks/sepolia.ts b/packages/config/src/chainConfig/networks/sepolia.ts index ab6facbec72e..51102cfafa7d 100644 --- a/packages/config/src/chainConfig/networks/sepolia.ts +++ b/packages/config/src/chainConfig/networks/sepolia.ts @@ -17,6 +17,7 @@ export const sepoliaChainConfig: ChainConfig = { // # Sunday, June 19, 2022 2:00:00 PM +UTC MIN_GENESIS_TIME: 1655647200, GENESIS_FORK_VERSION: b("0x90000069"), + GENESIS_DELAY: 86400, // Forking // --------------------------------------------------------------- diff --git a/packages/db/package.json b/packages/db/package.json index 6cfaf6ecce70..25aa5cdd2eea 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/db", - "version": "1.20.2", + "version": "1.21.0", "description": "DB modules of Lodestar", "author": "ChainSafe Systems", "homepage": "https://github.com/ChainSafe/lodestar#readme", @@ -36,12 +36,12 @@ }, "dependencies": { "@chainsafe/ssz": "^0.15.1", - "@lodestar/config": "^1.20.2", - "@lodestar/utils": "^1.20.2", + "@lodestar/config": "^1.21.0", + "@lodestar/utils": "^1.21.0", "classic-level": "^1.4.1", "it-all": "^3.0.4" }, "devDependencies": { - "@lodestar/logger": "^1.20.2" + "@lodestar/logger": "^1.21.0" } } diff --git a/packages/db/src/controller/level.ts b/packages/db/src/controller/level.ts index 07ebef8e8a22..084b577b6a01 100644 --- a/packages/db/src/controller/level.ts +++ b/packages/db/src/controller/level.ts @@ -44,7 +44,12 @@ export class LevelDbController implements DatabaseController { const db = - opts.db || new ClassicLevel(opts.name || "beaconchain", {keyEncoding: "binary", valueEncoding: "binary"}); + opts.db || + new ClassicLevel(opts.name || "beaconchain", { + keyEncoding: "binary", + valueEncoding: "binary", + multithreading: true, + }); try { await db.open(); diff --git a/packages/flare/package.json b/packages/flare/package.json index c735642ad8e4..461147274591 100644 --- a/packages/flare/package.json +++ b/packages/flare/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/flare", - "version": "1.20.2", + "version": "1.21.0", "description": "Beacon chain debugging tool", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -58,14 +58,14 @@ "blockchain" ], "dependencies": { - "@chainsafe/bls": "7.1.3", "@chainsafe/bls-keygen": "^0.4.0", - "@lodestar/api": "^1.20.2", - "@lodestar/config": "^1.20.2", - "@lodestar/params": "^1.20.2", - "@lodestar/state-transition": "^1.20.2", - "@lodestar/types": "^1.20.2", - "@lodestar/utils": "^1.20.2", + "@chainsafe/blst": "^2.0.3", + "@lodestar/api": "^1.21.0", + "@lodestar/config": "^1.21.0", + "@lodestar/params": "^1.21.0", + "@lodestar/state-transition": "^1.21.0", + "@lodestar/types": "^1.21.0", + "@lodestar/utils": "^1.21.0", "source-map-support": "^0.5.21", "yargs": "^17.7.1" }, diff --git a/packages/flare/src/cmds/selfSlashAttester.ts b/packages/flare/src/cmds/selfSlashAttester.ts index 6c42372f45c7..8b43e6a92cb0 100644 --- a/packages/flare/src/cmds/selfSlashAttester.ts +++ b/packages/flare/src/cmds/selfSlashAttester.ts @@ -1,5 +1,4 @@ -import bls from "@chainsafe/bls"; -import type {SecretKey} from "@chainsafe/bls/types"; +import {SecretKey, aggregateSignatures} from "@chainsafe/blst"; import {getClient} from "@lodestar/api"; import {phase0, ssz} from "@lodestar/types"; import {config as chainConfig} from "@lodestar/config/default"; @@ -149,5 +148,5 @@ function signAttestationDataBigint( const signingRoot = computeSigningRoot(ssz.phase0.AttestationDataBigint, data, proposerDomain); const sigs = sks.map((sk) => sk.sign(signingRoot)); - return bls.Signature.aggregate(sigs).toBytes(); + return aggregateSignatures(sigs).toBytes(); } diff --git a/packages/flare/src/cmds/selfSlashProposer.ts b/packages/flare/src/cmds/selfSlashProposer.ts index 0b71d5558af8..8b11ff2ce4dc 100644 --- a/packages/flare/src/cmds/selfSlashProposer.ts +++ b/packages/flare/src/cmds/selfSlashProposer.ts @@ -1,4 +1,4 @@ -import type {SecretKey} from "@chainsafe/bls/types"; +import {SecretKey} from "@chainsafe/blst"; import {getClient} from "@lodestar/api"; import {phase0, ssz} from "@lodestar/types"; import {config as chainConfig} from "@lodestar/config/default"; diff --git a/packages/flare/src/util/deriveSecretKeys.ts b/packages/flare/src/util/deriveSecretKeys.ts index 272cf87c09c4..5017fc6e7508 100644 --- a/packages/flare/src/util/deriveSecretKeys.ts +++ b/packages/flare/src/util/deriveSecretKeys.ts @@ -1,6 +1,5 @@ -import bls from "@chainsafe/bls"; -import type {SecretKey} from "@chainsafe/bls/types"; import {deriveEth2ValidatorKeys, deriveKeyFromMnemonic} from "@chainsafe/bls-keygen"; +import {SecretKey} from "@chainsafe/blst"; import {interopSecretKey} from "@lodestar/state-transition"; import {CliCommandOptions} from "@lodestar/utils"; import {YargsError} from "./errors.js"; @@ -42,7 +41,7 @@ export function deriveSecretKeys(args: SecretKeysArgs): SecretKey[] { return indexes.map((index) => { const {signing} = deriveEth2ValidatorKeys(masterSK, index); - return bls.SecretKey.fromBytes(signing); + return SecretKey.fromBytes(signing); }); } diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index 2da624af4581..70673b210149 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.20.2", + "version": "1.21.0", "type": "module", "exports": "./lib/index.js", "types": "./lib/index.d.ts", @@ -37,11 +37,11 @@ }, "dependencies": { "@chainsafe/ssz": "^0.15.1", - "@lodestar/config": "^1.20.2", - "@lodestar/params": "^1.20.2", - "@lodestar/state-transition": "^1.20.2", - "@lodestar/types": "^1.20.2", - "@lodestar/utils": "^1.20.2" + "@lodestar/config": "^1.21.0", + "@lodestar/params": "^1.21.0", + "@lodestar/state-transition": "^1.21.0", + "@lodestar/types": "^1.21.0", + "@lodestar/utils": "^1.21.0" }, "keywords": [ "ethereum", diff --git a/packages/light-client/README.md b/packages/light-client/README.md index dd00777f81b5..cd3a0265008a 100644 --- a/packages/light-client/README.md +++ b/packages/light-client/README.md @@ -18,15 +18,14 @@ The evolution of light clients is emblematic of the broader trajectory of Ethere ## Requirements for Running a Light-Client -Access to an beacon node that supports the light client specification is necessary. The client must support the following routes from the [consensus API spec](https://github.com/ethereum/consensus-specs/tree/dev): +Access to an beacon node that supports the light client specification is necessary. The client must support the following routes from the [consensus API spec](https://github.com/ethereum/beacon-APIs/tree/v2.5.0/apis/beacon/light_client): -- `/eth/v1/beacon/light_client/updates` -- `/eth/v1/beacon/light_client/optimistic_update` -- `/eth/v1/beacon/light_client/finality_update` -- `/eth/v1/beacon/light_client/bootstrap/{block_root}` -- `/eth/v0/beacon/light_client/committee_root` +- [`GET /eth/v1/beacon/light_client/updates`](https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.5.0#/Beacon/getLightClientUpdatesByRange) +- [`GET /eth/v1/beacon/light_client/optimistic_update`](https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.5.0#/Beacon/getLightClientOptimisticUpdate) +- [`GET /eth/v1/beacon/light_client/finality_update`](https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.5.0#/Beacon/getLightClientFinalityUpdate) +- [`GET /eth/v1/beacon/light_client/bootstrap/{block_root}`](https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.5.0#/Beacon/getLightClientBootstrap) -System requirements are quite low so its possible to run a light client in the browser as part of a website. There are a few examples of this on github that you can use as reference, our [prover](https://chainsafe.github.io/lodestar/lightclient-prover/prover) being one of them. +System requirements are quite low so its possible to run a light client in the browser as part of a website. There are a few examples of this on github that you can use as reference, our [prover](https://chainsafe.github.io/lodestar/libraries/lightclient-prover/prover) being one of them. You can find more information about the light-client protocol in the [specification](https://github.com/ethereum/consensus-specs). @@ -42,8 +41,8 @@ It is possible to start up the light-client as a standalone process. ```bash lodestar lightclient \ --network sepolia \ - --beacon-api-url https://lodestar-sepolia.chainsafe.io \ - --checkpoint-root "0xccaff4b99986a7b05e06738f1828a32e40799b277fd9f9ff069be55341fe0229" + --beaconApiUrl https://lodestar-sepolia.chainsafe.io \ + --checkpointRoot "0xccaff4b99986a7b05e06738f1828a32e40799b277fd9f9ff069be55341fe0229" ``` ## Light-Client Programmatic Example @@ -93,12 +92,12 @@ lightclient.emitter.on(LightclientEvent.lightClientOptimisticHeader, async (opti ## Browser Integration -If you want to use Lightclient in browser and facing some issues in building it with bundlers like webpack, vite. We suggest to use our distribution build. The support for single distribution build is started from `1.19.0` version. +If you want to use Lightclient in browser and facing some issues in building it with bundlers like webpack, vite. We suggest to use our distribution build. The support for single distribution build is started from `1.20.0` version. Directly link the dist build with the `